反应值与反应值

这个问题与 这个有关。两者可以生成相同的功能,但实现略有不同。一个重要的区别是 reactiveValue是一个可以有多个值的容器,比如 input$。在 闪亮的文件中,功能通常使用 reactive()来实现,但在大多数情况下,我发现 reactiveValues()更方便。有什么隐情吗?这两者之间还有什么我没注意到的重大区别吗?这两个代码片段是等价的吗?

请参阅使用以下方法实现的相同 示例代码:

  1. 一种反应性的表情:

    library(shiny)
    
    
    ui <- fluidPage(
    shiny::numericInput(inputId = 'n',label = 'n',value = 2),
    shiny::textOutput('nthValue'),
    shiny::textOutput('nthValueInv')
    )
    
    
    fib <- function(n) ifelse(n<3, 1, fib(n-1)+fib(n-2))
    
    
    server<-shinyServer(function(input, output, session) {
    currentFib         <- reactive({ fib(as.numeric(input$n)) })
    output$nthValue    <- renderText({ currentFib() })
    output$nthValueInv <- renderText({ 1 / currentFib() })
    })
    
    
    shinyApp(ui = ui, server = server)
    
  2. a reactive value:

    library(shiny)
    
    
    ui <- fluidPage(
    shiny::numericInput(inputId = 'n',label = 'n',value = 2),
    shiny::textOutput('nthValue'),
    shiny::textOutput('nthValueInv')
    )
    
    
    fib <- function(n) ifelse(n<3, 1, fib(n-1)+fib(n-2))
    
    
    server<-shinyServer(function(input, output, session) {
    myReactives <- reactiveValues()
    observe(  myReactives$currentFib <-  fib(as.numeric(input$n))  )
    output$nthValue    <- renderText({ myReactives$currentFib  })
    output$nthValueInv <- renderText({ 1 / myReactives$currentFib  })
    })
    
    
    shinyApp(ui = ui, server = server)
    
45367 次浏览

It's true that the two constructs are similar, and that many times you can use either one to solve your problem. But it usually makes more sense to use one or the other.

In the fibonacci case, I think using a reactive() expression makes more sense, because currentFib is a value that should be modified in very specific predictable times (ie. when input$n changes, the reactive value should be updated accordingly, or react to that change).

But in some other cases it might be simpler and better to use reactiveValues. I will show two examples.

First, whenever you have a variable that you think of as having some sort of state (rather than just reacting to a different value being updated), I think using reactiveValues is better.

Example:

library(shiny)


ui <- fluidPage(
"Total:",
textOutput("total", inline = TRUE),
actionButton("add1", "Add 1"),
actionButton("add5", "Add 5")
)


server <- function(input, output, session) {
values <- reactiveValues(total = 0)


observeEvent(input$add1, {
values$total <- values$total + 1
})
observeEvent(input$add5, {
values$total <- values$total + 5
})
output$total <- renderText({
values$total
})
}


shinyApp(ui = ui, server = server)

In the code above, we have a total variable that has mutable state, and it's much more intuitive to think of it as a typical variable and use it as such. This is the most common case when I use reactiveValues.

I also use reactiveValues when a variable can be updated in multiple places. To borrow from the fibonacci example, consider the following shiny app, where the n number can be set by either one of two inputs:

library(shiny)


fib <- function(n) ifelse(n < 3, 1, fib(n - 1) + fib(n - 2))


ui <- fluidPage(
selectInput("nselect", "Choose a pre-defined number", 1:10),
numericInput("nfree", "Or type any number", 1),
"Fib number:",
textOutput("nthval", inline = TRUE)
)


server <- function(input, output, session) {
values <- reactiveValues(n = 1)


observeEvent(input$nselect, {
values$n <- input$nselect
})
observeEvent(input$nfree, {
values$n <- input$nfree
})
output$nthval <- renderText({
fib(as.integer(values$n))
})
}


shinyApp(ui = ui, server = server)

This example might seem a bit strange in the context of fibonacci, but hopefully you can see how in some other complex apps, sometimes you may wish to set the value of a variable in different places and it can be more intuitive to do it using a reactiveValue rather than a reactive expression that must be implemented in one block.

Hopefully this was helpful and makes sense. Of course this is only my personal take on the subject, it's not necessarily what the shiny developers intended, but this is how I've learned to use the two methods.

There is a catch, though it won't come into play in your example.

The shiny developers designed reactive() to be lazy, meaning that the expression contained in it will only be executed when it is called by one of its dependents. When one of its reactive dependencies is changed, it clears its cache and notifies its own dependents, but it is not itself executed until asked to by one of those dependents. (So if, say, its sole dependent is a textOutput() element on a hidden tab, it won't actually be executed unless and until that tab is opened.)

observe(), on the other hand, is eager; the expression that it contains will be executed right away whenever one of its reactive dependencies is changed -- even if it's value is not needed by any of its dependents (and in fact even if has no dependents). Such eagerness is desirable when you're calling observe() for its side-effects, but it can be wasteful when you're only using it to pass on the return value of its contents to other reactive expressions or endpoints down the line.

Joe Cheng explains this distinction quite well in his 2016 Shiny Developer Conference presentation on "Effective reactive programming", available here. See especially the bit starting around 30:20 in the presentation's second hour. If you watch until 40:42 (blink and you'll miss it!) he briefly characterizes the behavior of the observe()/reactiveValue () combination that you like.