0Pricing
R Academy · 课时

使用 renderUI 和 insertUI 创建动态用户界面

根据用户输入和服务器状态即时生成用户界面元素

使用 renderUI 和 insertUI 创建动态用户界面 是 CoddyKit 上的免费 R Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 R Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 R Academy 课程共包含 4 节课。

动态用户界面 — 原因与时机

有时,应用启动时无法完全确定所需的用户界面,它取决于用户选择、已加载的数据或运行时条件。Shiny 提供两种机制:renderUI() 用于替换占位符的全部内容,insertUI()/removeUI() 用于添加或删除元素,而无需全部替换。

library(shiny)

# Static UI: columns always shown
# ui <- fluidPage(selectInput('x', ...), selectInput('y', ...))

# Dynamic UI: columns depend on which dataset was loaded
# ui <- fluidPage(fileInput('upload', ...), uiOutput('var_selectors'))

renderUI() 与 uiOutput()

服务器中的 renderUI({...}) 会以响应式方式构建并返回一个用户界面元素。用户界面中的 uiOutput('id') 会创建一个占位符,用于显示 renderUI 生成的内容。每次响应式代码重新运行时,占位符的全部内容都会被替换。

ui <- fluidPage(
  selectInput('dataset', 'Dataset:', choices = c('iris', 'mtcars')),
  uiOutput('column_selector')   # placeholder
)

server <- function(input, output, session) {
  output$column_selector <- renderUI({
    df <- get(input$dataset)  # reactive dependency on dataset
    selectInput('col', 'Choose Column:',
                choices = names(df))
  })
}

shinyApp(ui, server)

使用 renderUI() 创建多个控件

从 renderUI() 返回一个 tagList(),即可一次渲染多个用户界面元素。当控件数量取决于数据时,这是标准做法,例如为数据集中的每一列生成一个筛选器。

server <- function(input, output, session) {
  output$filters <- renderUI({
    df    <- get(input$dataset)
    nums  <- names(df)[sapply(df, is.numeric)]

    # One slider per numeric column
    sliders <- lapply(nums, function(col) {
      rng <- range(df[[col]], na.rm = TRUE)
      sliderInput(
        inputId = paste0('filter_', col),
        label   = col,
        min     = rng[1], max = rng[2],
        value   = rng
      )
    })
    tagList(sliders)   # render all sliders
  })
}

insertUI() — 动态添加元素

insertUI(selector, where, ui) 会将用户界面元素插入 DOM,而无需重新构建整个页面。请使用 CSS 选择器定位插入位置。where 可以是 'afterEnd'、'beforeEnd'、'afterBegin' 或 'beforeBegin'。

server <- function(input, output, session) {
  item_count <- reactiveVal(0)

  observeEvent(input$add_item, {
    item_count(item_count() + 1)
    id <- paste0('item_', item_count())

    insertUI(
      selector = '#item_container',  # CSS selector
      where    = 'beforeEnd',
      ui       = div(
        id    = id,
        style = 'padding: 5px; border: 1px solid #ccc; margin: 3px;',
        textInput(id, paste('Item', item_count()), value = '')
      )
    )
  })
}

removeUI() — 删除元素

removeUI(selector) 会删除与 CSS 选择器匹配的第一个 DOM 元素。使用 multiple = TRUE 可删除所有匹配的元素。对于用户可以添加和删除项目的列表式用户界面,这是 insertUI() 的对应操作。

server <- function(input, output, session) {
  observeEvent(input$remove_last, {
    # Remove the last item div
    n <- isolate(item_count())
    if (n > 0) {
      removeUI(selector = paste0('#item_', n))
      item_count(n - 1)
    }
  })

  observeEvent(input$clear_all, {
    # Remove all items matching a class
    removeUI(selector = '.dynamic-item', multiple = TRUE)
    item_count(0)
  })
}

conditionalPanel() — 客户端可见性

conditionalPanel(condition, ...) 会根据在浏览器中计算的 JavaScript 条件显示或隐藏用户界面元素。与 renderUI() 不同,这些元素始终存在于 DOM 中,只是显示或隐藏而已。对于简单的显示/隐藏逻辑,这种方式速度更快,因为不需要服务器往返。

ui <- fluidPage(
  selectInput('plot_type', 'Plot Type:',
              choices = c('scatter', 'histogram', 'boxplot')),

  # Only shown when scatter is selected
  conditionalPanel(
    condition = "input.plot_type == 'scatter'",
    selectInput('x_var', 'X Variable:', choices = names(mtcars)),
    selectInput('y_var', 'Y Variable:', choices = names(mtcars))
  ),

  # Only shown when histogram is selected
  conditionalPanel(
    condition = "input.plot_type == 'histogram'",
    sliderInput('bins', 'Number of bins:', 5, 50, 20)
  ),

  plotOutput('main_plot')
)

updateSelectInput() — 修改现有输入

无需替换整个输入控件,可以使用 updateSelectInput(session, id, choices, selected),只更新现有 selectInput 的选项或选中值。所有输入类型都有类似的更新函数,例如 updateSliderInput()、updateTextInput() 等。

server <- function(input, output, session) {
  # When dataset changes, update the column selector
  observeEvent(input$dataset, {
    df <- get(input$dataset)
    updateSelectInput(
      session  = session,
      inputId  = 'column',
      choices  = names(df),
      selected = names(df)[1]
    )
  })

  # Reset slider when reset button pressed
  observeEvent(input$reset_btn, {
    updateSliderInput(session, 'n_items',
                     value = 20, min = 1, max = 100)
  })
}

updateSliderInput() 与 updateNumericInput()

更新函数会向浏览器发送消息,以修改现有输入的当前值、最小值、最大值或标签。与使用 renderUI() 销毁并重新创建控件相比,这种方式效率更高,并且会保留控件在页面布局中的位置。

server <- function(input, output, session) {
  # When data loads, set slider max to actual data length
  observeEvent(input$load_data, {
    data <- readRDS(input$upload$datapath)

    updateSliderInput(session, 'row_slider',
      min   = 1,
      max   = nrow(data),
      value = c(1, min(50, nrow(data)))
    )

    updateNumericInput(session, 'page_size',
      max   = nrow(data),
      value = min(10, nrow(data))
    )
  })
}

使用 req() 访问 renderUI 输入

使用 renderUI() 动态创建的输入在渲染前为 NULL。请在所有依赖动态创建输入的服务器代码中使用 req(input$dynamic_input),以避免初始化期间发生错误。

server <- function(input, output, session) {
  # dynamic input: 'col' is created by renderUI
  output$column_selector <- renderUI({
    selectInput('col', 'Column:', choices = names(get(input$dataset)))
  })

  output$summary_stats <- renderPrint({
    req(input$col)           # wait until 'col' exists
    req(input$dataset)       # and dataset is chosen
    df <- get(input$dataset)
    summary(df[[input$col]])
  })
}

使用 appendTab() 创建动态选项卡

appendTab(inputId, tab, select) 会在运行时向 tabsetPanel() 添加新选项卡。removeTab(inputId, target) 会删除一个选项卡。这种模式支持这样的工作流:用户可以打开、关闭并切换动态生成的分析视图。

ui <- fluidPage(
  actionButton('add_tab', 'Open New Analysis Tab'),
  tabsetPanel(id = 'analysis_tabs', type = 'tabs')
)

server <- function(input, output, session) {
  tab_count <- reactiveVal(0)

  observeEvent(input$add_tab, {
    tab_count(tab_count() + 1)
    n <- tab_count()
    appendTab(
      inputId = 'analysis_tabs',
      select  = TRUE,
      tab = tabPanel(
        title = paste('Analysis', n),
        value = paste0('tab_', n),
        plotOutput(paste0('plot_', n)),
        actionButton(paste0('close_', n), 'Close')
      )
    )
  })
}

renderUI 与 insertUI — 如何选择

请根据使用场景选择合适的工具:

  • renderUI():以响应式方式替换占位符的全部内容。适用于整个区块会一起变化的情况。
  • insertUI():向现有内容添加新元素。适用于不断增长的列表,且不希望重新渲染全部内容的情况。
  • updateXxxInput():就地修改现有输入。适用于更改选项或值而无需重新渲染的情况。
  • conditionalPanel():无需服务器往返即可显示或隐藏。适用于简单的可见性逻辑。
# Decision guide:
# - 'Show filter only when advanced mode' -> conditionalPanel
# - 'Populate column choices from loaded CSV' -> updateSelectInput
# - 'Show different controls per selected tab' -> renderUI
# - 'Add a row editor for each row user adds' -> insertUI
# - 'Remove a row editor' -> removeUI

# Combining strategies:
server <- function(input, output, session) {
  # Use renderUI for column selectors (replaces whole block)
  output$col_selectors <- renderUI({ ... })
  # Use insertUI for dynamically added rows
  observeEvent(input$add_row, { insertUI(...) })
  # Use conditionalPanel for show/hide (in UI, no server code)
}

快速检查

在 Shiny 中构建动态用户界面时,renderUI() 和 insertUI() 的关键区别是什么?

动态用户界面回顾

使用 renderUI 和 insertUI 构建动态用户界面的要点:

  • renderUI({}) + uiOutput('id'):以响应式方式替换占位符的全部内容。
  • 从 renderUI() 返回 tagList(),即可一次渲染多个元素。
  • insertUI(selector, where, ui):将元素插入任意 DOM 位置。
  • removeUI(selector):通过 CSS 选择器删除元素。
  • conditionalPanel(condition, ...):在客户端显示或隐藏,无需服务器参与。
  • updateSelectInput()、updateSliderInput():就地修改现有输入。
  • 在动态输入渲染之前,始终使用 req(input$dynamic_id) 防止其为 NULL。
# Typical pattern: dynamic column selector
ui <- fluidPage(
  fileInput('upload', 'Upload CSV'),
  uiOutput('col_ui'),          # placeholder
  plotOutput('col_plot')
)

server <- function(input, output, session) {
  df <- reactive({
    req(input$upload)
    read.csv(input$upload$datapath)
  })

  output$col_ui <- renderUI({
    req(df())
    selectInput('col', 'Column:', choices = names(df()))
  })

  output$col_plot <- renderPlot({
    req(input$col)
    hist(df()[[input$col]])
  })
}

常见问题解答

「使用 renderUI 和 insertUI 创建动态用户界面」课时是免费的吗?

是的 — 「使用 renderUI 和 insertUI 创建动态用户界面」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 R Academy 课程的其余内容,请升级到 CoddyKit PRO。 R Academy 课程共包含 4 节课。

「使用 renderUI 和 insertUI 创建动态用户界面」这节课中我会学到什么?

根据用户输入和服务器状态即时生成用户界面元素 你通过在浏览器中直接运行的动手代码来练习 R Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 R Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 R Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「使用 renderUI 和 insertUI 创建动态用户界面」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 R Academy 课中编写并运行代码吗?

能。每节 R Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 深入理解响应式编程
  2. 使用 Shiny 模块复用代码
  3. 使用 renderUI 和 insertUI 创建动态用户界面
  4. 部署 Shiny 应用
← 返回 R Academy