反应式与观察与观察事件的优势

Posted

技术标签:

【中文标题】反应式与观察与观察事件的优势【英文标题】:Advantages of reactive vs. observe vs. observeEvent 【发布时间】:2019-03-31 16:01:08 【问题描述】:

我已经阅读了有关闪亮反应式编程的所有内容。我有点困惑。以下所有方法都有效,但首选方法是什么,为什么?显然,下面的示例很简单,但是在使用任何方法创建更大的应用程序时我会遇到麻烦吗?

我一直倾向于使用服务器代码 #1 中的样式。原因是,我能够分解 if 语句。对我来说,这似乎更具可读性。同样,下面的简单示例并不是非常复杂,但您可以轻松想象服务器代码 2 和服务器代码 3 会如何因大量嵌套的 if / if else 语句而变得非常混乱。

界面代码

library(shiny)

ui <- fluidPage(
  selectInput(inputId = 'choice',
              label = 'Choice',
              choice = c('Hello','Goodbye'),
              selected = c('Hello')
  ),

  textOutput('result')

)

服务器代码 1

server <- function(input,output,session)(

  text <- reactiveValues()

  observe(
    if (input$choice == 'Hello') 
      text$result <- 'Hi there'
      
    )

  observe(
    if (input$choice == 'Goodbye') 
      text$result <- 'See you later'
      
    )

  output$result <- renderText(
    text$result
  )

)

shinyApp(ui = ui, server = server)

服务器代码 2

server <- function(input,output,session)(

  getStatus <- reactive(

    if (input$choice == 'Hello') 
      'Hi there'
     else if (input$choice == 'Goodbye')
      'See you later'
    
  )

  output$result <- renderText(
    getStatus()
  )

)

shinyApp(ui = ui, server = server)

服务器代码 3

server <- function(input,output,session)(

  text <- reactiveValues()

  observeEvent(input$choice,
    if (input$choice == 'Hello') 
      text$result <- 'Hi there'
     else if (input$choice == 'Goodbye') 
      text$result <- 'See you later'
    
  )

  output$result <- renderText(
    text$result
  )

)

shinyApp(ui = ui, server = server)

【问题讨论】:

好问题,以及可重复的好例子。如果您还没有看过它,您可能想看看this related question 及其两个答案。 isolate(...) 与所有这些有何不同?它不会对内部命名的变量做出反应,在到达时执行,但仍然算作反应性环境,对吧? 请注意:您实际上可以像这样在 observeEvent 中监控多个变量:observeEvent(c(input$a,input$b),code) 【参考方案1】:

首先,这些东西有点模棱两可,在某些方面不是很直观,它甚至在 Shiny 博客上都这么说!

这是我对该主题的最佳理解..

让我们从reactive开始

反应函数允许用户监视输入或其他变化变量的状态,并返回值以在代码中的其他地方使用。对反应变量的监视被认为是惰性的,“反应表达式使用惰性求值;也就是说,当它们的依赖关系发生变化时,它们不会立即重新执行,而是等到它们被其他人调用。(@ 987654321@"。 您在示例 2 中很好地展示了这一点,因为您可以在 renderText 环境中调用该变量,一旦调用,响应式调用中的代码就会执行并重新评估该变量。

对于科学书呆子来说,这很像量子力学,通过调用反应变量(观察它)导致它通过重新评估而改变,过度拉伸?

现在observe

Observe 是类似的响应式,主要区别在于它不向任何其他环境返回任何值,除了它自己的,它不是懒惰的。观察函数持续监控其环境中所有反应值的任何变化,并在这些值发生变化时在其环境中运行代码。因此,观察不是“惰性”评估,因为它在重新评估之前不会等待被调用。再次注意,您不能从观察中分配变量。

为了实验:

server <- function(input,output,session)(

   observe(
   if (input$choice == 'Hello') 
      getStatus <- 'Hi there'
    
  )

  observe(
    if (input$choice == 'Goodbye') 
      getStatus <- 'See you later'
    
  )

  output$result <- renderText(
    getStatus
  )

)

shinyApp(ui = ui, server = server)

需要注意的是,在observe 中执行的代码期间,我们可以操作外部环境反应变量。在您的情况下,您分配text &lt;- reactiveValues(),然后通过调用text$result &lt;- 'Hi there' 对其进行操作。我们也可以做一些事情,比如更新selectInput 选择,或者其他闪亮的小部件,但是我们不能在这个环境中分配任何非反应性变量,比如上面例子中的 getStatus。而这个想法在observe文档中提到过,

“观察者就像一个响应式表达式,它可以读取响应式值并调用响应式表达式,并在这些依赖关系发生变化时自动重新执行。但与响应式表达式不同的是,它不会产生结果并且不能被用作其他反应式表达式的输入。因此,观察者仅对它们的副作用有用(例如,执行 I/O)(Source)"

最后,observeEvent

使用observeEvent 的最佳方式是将其视为已定义的触发器,因为它监视一个事件或变量的变化,然后在事件发生时触发。我最常使用它来观察按钮的输入,因为这是一个定义的事件,我希望在按下按钮后发生事情。它使用isolate 环境,我认为这是该功能如何工作的完美名称。

在这个环境中,我们可以调用一堆反应变量,但我们只定义一个作为触发器。 observeEventobserve 是触发器之间的主要区别,因为 observe 在任何更改时运行,而 observeEvent 等待触发器。请注意,此环境与 observable 的相似之处在于它不返回非反应性变量。

总结

这是一个将所有这些想法结合在一起的示例:

library(shiny)

ui<-
 fluidPage(
   fluidRow(
     column(4,
      h2("Reactive Test"),
      textInput("Test_R","Test_R"),
      textInput("Test_R2","Test_R2"),
      textInput("Test_R3","Test_R3"),
      tableOutput("React_Out")
    ),
     column(4,
      h2("Observe Test"),
      textInput("Test","Test"),
      textInput("Test2","Test2"),
      textInput("Test3","Test3"),
      tableOutput("Observe_Out")
    ),
    column(4,
      h2("Observe Event Test"),
      textInput("Test_OE","Test_OE"),
      textInput("Test_OE2","Test_OE2"),
      textInput("Test_OE3","Test_OE3"),
      tableOutput("Observe_Out_E"),
      actionButton("Go","Test")
    )

    ),
  fluidRow(
    column(8,
    h4("Note that observe and reactive work very much the same on the surface,
       it is when we get into the server where we see the differences, and how those
       can be exploited for diffrent uses.")
  ))

  )

server<-function(input,output,session)

# Create a reactive Evironment. Note that we can call the varaible outside same place
# where it was created by calling Reactive_Var(). When the varaible is called by
# renderTable is when it is evaluated. No real diffrence on the surface, all in the server.

Reactive_Var<-reactive(c(input$Test_R, input$Test_R2, input$Test_R3))

output$React_Out<-renderTable(
  Reactive_Var()
  )

# Create an observe Evironment. Note that we cannot access the created "df" outside 
# of the env. A, B,and C will update with any input into any of the three Text Feilds.
observe(
  A<-input$Test
  B<-input$Test2
  C<-input$Test3
  df<-c(A,B,C)
  output$Observe_Out<-renderTable(df)
  )

#We can change any input as much as we want, but the code wont run until the trigger
# input$Go is pressed.
observeEvent(input$Go, 
  A<-input$Test_OE
  B<-input$Test_OE2
  C<-input$Test_OE3
  df<-c(A,B,C)
  output$Observe_Out_E<-renderTable(df)
)


shinyApp(ui, server)

reactive 创建一个变量,该变量可以通过用户输入随时间更改,仅在调用时评估“惰性”含义。

observe 持续监控反应事件和变量,每当ANY反应变量在环境(观察到的环境)中发生变化时,就会评估代码。可以更改先前定义的反应变量的值,不能创建/返回变量。

observeEvent(多米诺骨牌效应) 持续监控 ONE 定义的反应变量/事件(触发器)并在触发器被触发器的更改/输入激活时运行代码。可以更改先前定义的反应变量的值,不能创建/返回变量。

eventReactive 创建变量,定义的触发器类似于observeEvent。当您想要一个由于触发器而不是在调用它时评估的反应变量时,请使用此选项。

我希望这会有所帮助,如果我的理解有误或有更多说明,请随时编辑此答案。

【讨论】:

隔离与所有这些有何不同? Isolate 通常在另一个函数中使用,但我只看到了一个没有使用的示例:github.com/rstudio/shiny/issues/141#issuecomment-351857670 isolate(...) 不会对内部命名的变量做出反应,在到达时执行,但仍算作反应性环境,对吧?【参考方案2】:

已经有很详细的答案了,我就自己加个简单的两分钱吧:

尽可能坚持使用reactive() 而不是reactiveValues()。普通的reactive() 更符合shiny 的反应式编程 理念,这意味着reactive() 表达式只是告诉shiny 如何计算变量,而无需指定。 Shiny 将负责确定何时计算它。他们会被懒惰地评估(仅在需要时),他们会缓存他们的价值,他们会使用书签功能 - 这正是闪亮的设计方式,应该始终是首选。

使用reactiveValues(),您现在回到了更多命令式编程领域,而不是被动式编程。在某些情况下,reactive() 无法削减它,您需要使用reactiveValues()(或reactiveVal()),但只有在reactive() 不起作用时才应使用它们。例如,reactive() 只有一个地方定义了变量,所以如果你想在多个地方定义变量,你需要使用reactiveValues()更完整的解释reactive()reactiveValues()的区别可以看my answer from an old post

observe() vs observeEvent():你可以认为它们是一样的,但observeEvent() 只是observe() 的一个快捷方式,由某些变量触发,其余代码为isolate() -ed。事实上,你用observeEvent() 做的任何事情都可以用observe() 做,这是同一件事的两种风格。

【讨论】:

以上是关于反应式与观察与观察事件的优势的主要内容,如果未能解决你的问题,请参考以下文章

反应器模式 vs 观察者模式

深入理解观察者模式

在 Angular 6 中使用 rxjs 可观察对象的目的是啥?与 async/await 相比,rxjs 的优势是啥? [复制]

观察者模式与事件委托

观察者模式和反应式编程有啥区别?

定制事件 观察者模式