0Pricing
R Academy · 课时

深入理解响应式编程

理解响应式值、响应式表达式、观察者和响应式图

深入理解响应式编程 是 CoddyKit 上的免费 R Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 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) 会在 eventExpr 发生变化时运行 handlerExpr。它用于执行副作用,例如更新状态、写入数据库、下载文件或切换选项卡。处理程序不会返回值。

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

响应式隔离反模式

一种常见错误是:在非响应式上下文中意外读取响应式值,或者在本应隔离的计算中读取响应式值,从而创建不需要的依赖。请始终明确区分:哪些位置需要创建依赖,哪些位置只需将状态读取为一次性快照。

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()) })
}

常见问题解答

「深入理解响应式编程」课时是免费的吗?

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

「深入理解响应式编程」这节课中我会学到什么?

理解响应式值、响应式表达式、观察者和响应式图 你通过在浏览器中直接运行的动手代码来练习 R Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 R Academy 需要有经验吗?

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

「深入理解响应式编程」课时需要多长时间?

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

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

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

此课程中的所有课时

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