如何将reactiveValues替代reactiveVal作为Shiny模块参数?
Shiny模块中用reactiveValues控制shinyjs组件状态的问题
可运行最简示例
我尝试用shinyjs包禁用模块UI,主应用中使用reactiveVal()定义的enabled响应式参数可以正常运行:
library(shiny) library(shinyjs) # 子模块UI subUI <- function(id) { ns <- NS(id) tagList( useShinyjs(), actionButton(ns("click"), "CLICK!") ) } # 子模块服务器 subServer <- function(id, enabled) { stopifnot(is.reactive(enabled)) moduleServer( id, function(input, output, session) { observe({ if(enabled()) { enable("click") } else { disable("click") } }) } ) } # 主应用 ui <- fluidPage( actionButton("toggle", "Toggle"), subUI("sub") ) server <- function(input, output, session) { enabled <- reactiveVal(TRUE) subServer("sub", enabled) observeEvent(input$toggle, { enabled(! enabled()) }) } shinyApp(ui, server, options = list( launch.browser = TRUE ))
问题
实际应用中需要处理大量响应式值,因此改用reactiveValues(),但无法适配原有逻辑,尝试的代码如下:
library(shiny) library(shinyjs) # 子模块UI subUI <- function(id) { ns <- NS(id) tagList( useShinyjs(), actionButton(ns("click"), "CLICK!") ) } # 子模块服务器 subServer <- function(id, enabled) { # stopifnot(is.reactive(enabled)) moduleServer( id, function(input, output, session) { observe({ if(enabled) { enable("click") } else { disable("click") } }) } ) } # 主应用 ui <- fluidPage( actionButton("toggle", "Toggle"), subUI("sub") ) server <- function(input, output, session) { r <- reactiveValues() r$enabled <- TRUE subServer("sub", r$enabled) observeEvent(input$toggle, { r$enabled <- ! r$enabled }) } shinyApp(ui, server, options = list( launch.browser = TRUE ))
必须注释掉子模块服务器中的stopifnot(is.reactive(enabled))才能运行,否则会触发错误:
Error in r$enabled :
Can't access reactive value 'enabled' outside of reactive consumer.
我以为is.reactive()是用来处理响应式值的,不清楚问题出在哪。
解决方案
问题根源
reactiveValues()是响应式容器,其中的单个属性(如r$enabled)不是独立的响应式对象,is.reactive(r$enabled)会返回FALSE;且直接在非响应式上下文(比如stopifnot所在的位置)访问r$enabled,会违反Shiny的响应式规则——响应式值只能在observe、reactive等响应式消费者内部访问。- 原代码传递
r$enabled给子模块时,传递的是当前的静态值,而非响应式引用,导致observe无法监听其变化,按钮状态不会更新。
修复方法
有两种可行的修复方式:
方式1:传递整个reactiveValues对象
修改子模块服务器,接收整个响应式容器,在响应式上下文内访问其属性:
library(shiny) library(shinyjs) # 子模块UI subUI <- function(id) { ns <- NS(id) tagList( useShinyjs(), actionButton(ns("click"), "CLICK!") ) } # 子模块服务器:接收整个reactiveValues对象 subServer <- function(id, rv) { stopifnot(is.reactivevalues(rv)) # 检查是否为响应式容器 moduleServer( id, function(input, output, session) { observe({ if(rv$enabled) { enable("click") } else { disable("click") } }) } ) } # 主应用 ui <- fluidPage( actionButton("toggle", "Toggle"), subUI("sub") ) server <- function(input, output, session) { r <- reactiveValues(enabled = TRUE) subServer("sub", r) # 传递整个响应式容器 observeEvent(input$toggle, { r$enabled <- ! r$enabled }) } shinyApp(ui, server, options = list( launch.browser = TRUE ))
方式2:将单个属性包装为reactive()对象传递
如果不想传递整个容器,可把r$enabled包装成独立的响应式对象:
library(shiny) library(shinyjs) # 子模块UI subUI <- function(id) { ns <- NS(id) tagList( useShinyjs(), actionButton(ns("click"), "CLICK!") ) } # 子模块服务器:保持原有接收响应式对象的逻辑 subServer <- function(id, enabled) { stopifnot(is.reactive(enabled)) moduleServer( id, function(input, output, session) { observe({ if(enabled()) { enable("click") } else { disable("click") } }) } ) } # 主应用 ui <- fluidPage( actionButton("toggle", "Toggle"), subUI("sub") ) server <- function(input, output, session) { r <- reactiveValues(enabled = TRUE) # 将r$enabled包装为reactive对象传递 subServer("sub", reactive(r$enabled)) observeEvent(input$toggle, { r$enabled <- ! r$enabled }) } shinyApp(ui, server, options = list( launch.browser = TRUE ))
关键提示
is.reactive()用于判断对象是否是reactiveVal()或reactive()创建的独立响应式对象,而reactiveValues()创建的容器需要用is.reactivevalues()判断。- 传递响应式值给子模块时,必须传递响应式对象本身,而非对象的当前静态值,这样子模块才能监听其变化。
内容的提问来源于stack exchange,提问作者pietrodito
相关产品推荐
相关产品推荐

