0Pricing
R Academy · Урок

Глубокое погружение в реактивное программирование

Изучите реактивные значения, реактивные выражения, наблюдатели и граф реактивности

«Глубокое погружение в реактивное программирование» — бесплатный урок R Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения R Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс R Academy содержит 4 уроков всего.

Реактивность: основная идея

Модель реактивного программирования Shiny автоматически отслеживает зависимости между вычислениями. Когда входное значение меняется, повторно выполняются только зависящие от него выходы — и ничего больше. Понимание этого графа зависимостей — ключ к созданию эффективных приложений Shiny без ошибок.

library(shiny)

# Simplest reactive relationship:
# output depends on input — Shiny tracks this automatically
server <- function(input, output, session) {
  output$result <- renderText({
    paste('You typed:', input$text_in)  # depends on text_in
  })
}

reactive({}) — общие вычисления

reactive({...}) создаёт реактивное выражение, результат которого кэшируется и используется совместно. Если несколько выходов используют одно и то же ресурсоёмкое вычисление, оберните его в reactive(), чтобы выполнять его один раз при каждом изменении, а не один раз для каждого потребителя. Вызывайте его как функцию: data_subset().

server <- function(input, output, session) {
  # Computed once, shared by multiple outputs
  filtered_data <- reactive({
    subset(mtcars, cyl == input$cyl_select)
  })

  output$plot  <- renderPlot({ plot(filtered_data()$wt,
                                    filtered_data()$mpg) })
  output$table <- renderTable({ filtered_data() })
  # filtered_data() is only recomputed once when cyl_select changes
}

reactiveVal() — одно реактивное значение

reactiveVal(initial_value) создаёт реактивную переменную, которую можно программно читать и изменять. Для чтения вызовите её без аргументов, а для записи — с новым значением. Это удобно для хранения изменяемого состояния, от которого зависят несколько наблюдателей или выходов.

server <- function(input, output, session) {
  counter <- reactiveVal(0)   # initial value = 0

  observeEvent(input$increment, {
    counter(counter() + 1)    # read current, write new
  })

  observeEvent(input$reset, {
    counter(0)                 # reset to 0
  })

  output$count <- renderText(paste('Count:', counter()))
}

reactiveValues() — несколько переменных состояния

reactiveValues(key = value, ...) подобна именованному списку, каждый элемент которого является реактивным. Когда любой элемент изменяется, недействительными становятся только выходы, читающие этот элемент. Используйте её, чтобы объединять связанные переменные состояния, которые обновляются вместе.

server <- function(input, output, session) {
  state <- reactiveValues(
    data         = NULL,
    current_page = 1,
    total_rows   = 0
  )

  observeEvent(input$load_btn, {
    state$data       <- read.csv(input$file$datapath)
    state$total_rows <- nrow(state$data)
    state$current_page <- 1
  })

  output$info <- renderText({
    paste('Page', state$current_page, '| Rows:', state$total_rows)
  })
}

observeEvent() — реакция на события

observeEvent(eventExpr, handlerExpr) запускает handlerExpr при каждом изменении eventExpr. Эта конструкция используется для побочных эффектов: обновления состояния, записи в базу данных, скачивания файла или перехода на вкладку. Обработчик не возвращает значение.

server <- function(input, output, session) {
  observeEvent(input$save_btn, {
    # Runs once each time save_btn is clicked
    write.csv(current_data(), '/tmp/export.csv', row.names = FALSE)
    showNotification('Saved!', type = 'message')
  })

  observeEvent(input$reset_btn, {
    updateTextInput(session, 'search', value = '')
    updateSliderInput(session, 'range', value = c(0, 100))
  })
}

eventReactive() — Значение по запросу

eventReactive(eventExpr, valueExpr) похожа на reactive(), но пересчитывается только при возникновении определённого события (например, нажатия кнопки), а не при каждом изменении зависимостей. Используйте её, чтобы выполнять ресурсоёмкие вычисления только по явному запросу пользователя.

server <- function(input, output, session) {
  # Only re-run the model when 'Run Model' is clicked
  model_result <- eventReactive(input$run_btn, {
    # Expensive computation — only on button click
    lm(as.formula(input$formula), data = get(input$dataset))
  })

  output$summary <- renderPrint({
    summary(model_result())
  })
}

isolate() — Чтение без зависимости

isolate({expr}) считывает реактивное значение или выражение, не создавая реактивную зависимость. Окружающее вычисление НЕ будет запущено повторно при изменении изолированного значения. Используйте эту конструкцию внутри observe() или observeEvent(), чтобы получить текущее состояние, не подписываясь на изменения.

server <- function(input, output, session) {
  observeEvent(input$add_row_btn, {
    # Read current_data without depending on it
    current <- isolate(data_rv())
    new_row <- data.frame(
      id    = nrow(current) + 1,
      value = input$new_value
    )
    data_rv(rbind(current, new_row))
  })
}

invalidateLater() — Автоматическое обновление

invalidateLater(ms) внутри реактивного контекста планирует его повторное выполнение через ms миллисекунд. Это используется для периодического опроса: обновления потока данных в реальном времени, показа текущего времени или регулярной проверки появления новых строк в базе данных.

server <- function(input, output, session) {
  live_data <- reactive({
    invalidateLater(5000)   # re-run every 5 seconds
    # Fetch fresh data from external source
    httr2::request('https://api.example.com/stats') |>
      httr2::req_perform() |>
      httr2::resp_body_json()
  })

  output$live_plot <- renderPlot({
    plot(live_data()$time, live_data()$value, type = 'l')
  })
}

reactlog — Отладка реактивного графа

Пакет reactlog визуализирует граф реактивных зависимостей. Включите его перед запуском приложения и вызовите reactlog_show() после взаимодействия с приложением, чтобы увидеть, какие реактивные узлы были признаны недействительными и в каком порядке. Это незаменимый инструмент для отладки сложных приложений.

library(reactlog)

# Enable reactlog BEFORE launching the app
reactlog_enable()

# Launch your app
shinyApp(ui, server)

# After interacting in the browser, in the R console:
shiny::reactlogShow()

# A viewer opens showing the reactive graph
# with timestamps and invalidation chains

Антипаттерн изоляции реактивных зависимостей

Распространённая ошибка — случайно считывать реактивное значение в нерeaктивном контексте или создавать нежелательные зависимости, считывая реактивное значение внутри вычисления, которое должно быть изолировано. Всегда осознанно определяйте, где создавать зависимости, а где считывать состояние как одномоментный снимок.

server <- function(input, output, session) {
  # WRONG: output depends on input$name, but also re-runs
  # every time input$slider changes (unintended dependency)
  output$msg <- renderText({
    paste(input$name, 'total:', input$slider * 2)
  })

  # CORRECT: output only responds to input$name changes;
  # slider is read as a snapshot
  output$msg_correct <- renderText({
    slider_val <- isolate(input$slider)
    paste(input$name, 'snapshot total:', slider_val * 2)
  })
}

observe() и observeEvent()

observe({...}) создаёт реактивный наблюдатель, который автоматически запускается повторно при изменении любого считываемого им реактивного значения. observeEvent(event, {...}) предоставляет больше контроля: она срабатывает только при определённом событии. Для побочных эффектов, запускаемых кнопкой, предпочтительнее observeEvent(), чтобы избежать непреднамеренного повторного выполнения.

server <- function(input, output, session) {
  # observe: re-runs on ANY change in input$x or input$y
  observe({
    cat('x or y changed:', input$x, input$y, '\n')
  })

  # observeEvent: only runs when button is clicked
  observeEvent(input$submit_btn, {
    cat('Form submitted with x =', isolate(input$x), '\n')
  }, ignoreNULL = TRUE, ignoreInit = TRUE)
}

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

В чём заключается ключевое различие между reactive({}) и eventReactive(event, {})?

Итоги реактивного программирования

Основные выводы из углублённого изучения реактивного программирования:

  • reactive({}): кэшируемое вычисление, запускается повторно при изменении зависимостей.
  • reactiveVal(init): одна изменяемая реактивная переменная; считывается с помощью val(), изменяется с помощью val(new).
  • reactiveValues(...): именованный список реактивных переменных состояния.
  • observeEvent(event, {}): побочные эффекты, запускаемые определённым событием.
  • eventReactive(event, {}): значение, вычисляемое только при возникновении определённого события.
  • isolate({}): считывание реактивного значения без создания зависимости.
  • invalidateLater(ms): планирование периодического повторного выполнения для обновлений в реальном времени.
  • Используйте reactlog для визуализации и отладки графа реактивных зависимостей.
server <- function(input, output, session) {
  # State
  rv <- reactiveValues(data = NULL, n = 0)

  # Load data on button click
  observeEvent(input$load, {
    rv$data <- read.csv(input$file$datapath)
    rv$n    <- nrow(rv$data)
  })

  # Expensive model only on 'Run' click
  model <- eventReactive(input$run, {
    lm(y ~ ., data = isolate(rv$data))
  })

  output$summary <- renderPrint({ summary(model()) })
}

Часто задаваемые вопросы

Урок «Глубокое погружение в реактивное программирование» бесплатный?

Да — полный текст урока «Глубокое погружение в реактивное программирование» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс R Academy, подпишись на CoddyKit PRO. Курс R Academy содержит 4 уроков всего.

Чему я научусь в уроке «Глубокое погружение в реактивное программирование»?

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

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

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

Сколько времени занимает урок «Глубокое погружение в реактивное программирование»?

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

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

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

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

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