0Pricing
R Academy · Урок

Динамический интерфейс с renderUI и insertUI

Создавайте элементы интерфейса на лету на основе пользовательского ввода и состояния сервера

«Динамический интерфейс с renderUI и insertUI» — бесплатный урок R Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения 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() с несколькими элементами управления

Возвращайте tagList() из renderUI(), чтобы одновременно отобразить несколько элементов интерфейса. Это стандартный подход, когда количество элементов управления зависит от данных — например, при создании отдельного фильтра для каждого столбца набора данных.

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) удаляет первый элемент DOM, соответствующий селектору CSS. Используйте 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))
    )
  })
}

Доступ к входным данным renderUI с помощью req()

Входные данные, созданные динамически с помощью 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)
}

Быстрая проверка

В чём заключается ключевое различие между renderUI() и insertUI() при создании динамического интерфейса в Shiny?

Итоги изучения динамического интерфейса

Основные выводы из темы «Динамический интерфейс с renderUI и insertUI»:

  • renderUI({}) + uiOutput('id'): реактивно заменяют всё содержимое заполнителя.
  • Возвращайте tagList() из renderUI(), чтобы одновременно отображать несколько элементов.
  • 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» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс R Academy, подпишись на CoddyKit PRO. Курс R Academy содержит 4 уроков всего.

Чему я научусь в уроке «Динамический интерфейс с renderUI и insertUI»?

Создавайте элементы интерфейса на лету на основе пользовательского ввода и состояния сервера Ты практикуешь R Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать R Academy?

Предыдущий опыт не требуется. R Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Динамический интерфейс с renderUI и insertUI»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке R Academy?

Да. Каждый урок R Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Глубокое погружение в реактивное программирование
  2. Модули Shiny для повторного использования кода
  3. Динамический интерфейс с renderUI и insertUI
  4. Развёртывание приложений Shiny
← Назад к R Academy