【问题标题】:R Shiny - Understanding the difference between observe and observeEvent when updating mutually dependent inputsR Shiny - 在更新相互依赖的输入时了解观察和观察事件之间的区别
【发布时间】:2020-12-15 17:24:00
【问题描述】:

下面的应用程序有两个相互依赖的数字输入,abinput$a 的值为1-input$binput$b 的值为1-input$a。每当用户更改输入的值时,我想相应地更新另一个的值。下面的代码包含两种方法:

  1. 单个观察者
  2. 两个独立的观察者 - 一个监听对 a 的更改并更新 b,另一个监听 b 并更新 a

当我尝试 1) 时,输入会陷入无限循环,但我不确定我是否理解原因。如果用户更改b,则触发观察者并且a 的值更新为1-b。由于两个输入的值现在都是最新的,因此input$ainput$b 没有进一步的更改,并且在用户进行另一项更改之前不应再次触发观察者。但事实并非如此。

方法 1) 与方法 2) 究竟有何不同?为什么 1) 会陷入循环而 2) 不会?

library(shiny)

shinyApp(
  ui = fluidPage(
    numericInput('a','a', value = 0.5, max = 1, min = 0),
    numericInput('b','b', value = 0.5, max = 1, min = 0)
  ),
  server = function(input, output, session) {

    # Using a single observer ----
    observe({

      updateNumericInput(session, 'a', value = 1-req(input$b))

      updateNumericInput(session, 'b', value = 1-req(input$a))

      print(input$a)

    })

    # Using separate observers ----
    # observeEvent(input$b, {
    #   updateNumericInput(session, 'a', value = 1-input$b)
    # })
    # 
    # observeEvent(input$a, {
    #   updateNumericInput(session, 'b', value = 1-input$a)
    # })

  }
)

【问题讨论】:

    标签: r shiny


    【解决方案1】:

    observeobserveEvent的文档中是这样写的:

    • observe

    观察者就像一个反应式表达式,它可以读取反应式值并调用反应式表达式,并在这些依赖关系发生变化时自动重新执行。 [...] [O] 观察者使用热切评估;一旦它们的依赖关系发生变化,它们就会安排自己重新执行。

    在您的示例中,observe 永远不会停止“观察”并在其他更改时立即更新输入,即使用户没有执行任何操作。因此,您将处于无限循环中,因为两个输入中的每一个都依赖于另一个。

    • observeEvent

    [S] 有时您希望等待用户执行特定操作,例如单击 actionButton(),然后再计算表达式或执行操作。 [...] 每当您想要执行某个操作以响应事件时,请使用 observeEvent。

    因此,observeEvent 需要用户做出一个动作来运行里面的函数。在您的示例中,仅当用户手动更改输入“b”时,输入“a”才会更新,反之亦然。这就是observeEvent 在这种情况下有效而observe 无效的原因。

    编辑:为了获得更清晰的使用observe 时创建的循环结果,以下是 OP 中示例的略微修改版本:

    library(shiny)
    
    shinyApp(
      ui = fluidPage(
        numericInput('a','a', value = NULL, max = 1, min = 0),
        numericInput('b','b', value = NULL, max = 1, min = 0)
      ),
      server = function(input, output, session) {
    
        # Using a single observer ----
        observe({
    
          updateNumericInput(session, 'a', value = 1-req(input$b))
    
          updateNumericInput(session, 'b', value = 1-req(input$a))
    
          print(paste0("a = ", input$a))
          print(paste0("b = ", input$b))
    
        })
    
    
        # Using separate observers ----
        # observeEvent(input$b, {
        #   updateNumericInput(session, 'a', value = 1-input$b)
        # })
        # 
        # observeEvent(input$a, {
        #   updateNumericInput(session, 'b', value = 1-input$a)
        # })
    
      }
    )
    

    注意解释:打印ab 的起始值。然后,ab 的新值被创建,但b 的新值取决于a 的第一个值(而不是a 的第二个值)。 ab 的第三个值将取决于ab 的第二个值等。

    【讨论】:

    • 感谢您的回答@bretauv。我知道observe 使用热切评估,但observeEvent 也是如此,对吧?我感到困惑的是为什么observe 即使在其依赖关系完成更改后仍会被触发。假设用户将b 更改为 0.54。然后a 的值更新为 0.46。任何一个输入都没有更多变化,为什么observe 正在运行?正如控制台中的打印输出所示,input$a 的值一直在 0.5 和 0.46 之间波动。
    • "eager evaluation" 在observeEvent 的文档中没有提到,所以我想它没有使用它。正如我在回答中所说,observe 不关心输入的修改是手动的还是自动计算的结果:只要其中一个输入发生变化,它就会继续计算。 observeEvent 不会:仅当手动更改另一个输入的值时,它才会计算输入的新值。假设您手动更改输入a。然后输入b 的值发生变化,因为observeEvent 检测到a 的变化...(续)
    • ... 但是b的修改是自动的,不是手动的。因此,observeEvent 不会由输入 b 的变化触发,并且您不会陷入无限循环。现在,如果您将b 更改为 0.54,为什么您的值会一直在 0.5 和 0.46 之间波动?首先,这是因为您忘记将print(input$b) 放在第一个print 的下方,所以只打印了a 的值。此外,print(input$a) 还打印应用程序开头的值 (0.5)。我已经编辑了我的帖子,以便输出更清晰。
    • observeEvent 需要用户做出动作来运行里面的函数”是不正确的。响应式表达式的任何更改都可以触发该操作,即使它不是由用户直接引起的。
    【解决方案2】:

    在第一种情况下,观察者查看其所有依赖项,此处为input$ainput$b。如果有任何变化,它会触发它的代码。现在,当 ab 更改时,观察者会更新 numericInputs。但它通知观察者它的依赖关系发生了变化,它必须再次运行。这就是为什么你最终会陷入无限循环。

    当您使用observeEvents 时,情况会有所不同。假设您设置a = 1。其中一个观察者被触发并更新b,所以b = 0。现在,另一个观察者被触发并更新a,所以a = 1。但在这种情况下,新值与旧值完全相同,因此 shiny 的内部反应机制会阻止它再次使 a 无效,因为它是多余的。

    【讨论】:

      猜你喜欢
      • 2018-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-11
      • 2018-06-18
      • 1970-01-01
      • 1970-01-01
      • 2023-02-09
      相关资源
      最近更新 更多