防止使用两个依赖路径双重执行 shiny reactive

prevent double execution of shiny reactive with two dependency pathways

考虑以下闪亮的应用程序。用户可以更改三个基本输入:AMS。 “内容”C(右边verbatimTextOuput中)直接依赖于AS.

S 可以通过两种方式更改:由用户更改,或通过更改 M/A。如果用户更改 S,则对 M 的依赖性无关紧要。 M如果为空也不使用。

情况如下图所示。

问题是当 M 不为空并且 A 被更改时:

  1. C 根据 old S 和新的 A
  2. 进行更新
  3. S 基于 M 和新的 A
  4. 进行更新
  5. C 根据新的 S 和新的 A.
  6. 进行更新

因此,C 被更新了两次,第一次是无效值。

希望发生的是 S 更新,基于新的 A,然后 C 更新基于新 S 和新 A.

要查看问题,运行 应用程序,然后:

  1. M 盒子里放东西
  2. 改变A
  3. 观察 C 改变了两次。

如何阻止第一次更新?

谢谢!


闪亮的应用程序代码:

library(shiny)
library(digest)

ui <- fluidPage(

    sidebarLayout(
        sidebarPanel(
           textInput("M", "M:", ""), 
           textInput("S", "S:", "testS"),
           selectInput("A", "A:", c("A1","A2"))
        ),
        
        mainPanel(
           verbatimTextOutput("C")
        )
    )
)

server <- function(input, output, session) {
    
    # Count calculations of C
    count = 0
    
    # Make reactive so we can modify the value
    # in the input box (inpt$S is an input and output,
    # essentially)
    S <- reactive({
        input$S
    })
    
    # Create "content" from A and S
    C <- reactive({
            count <<- count + 1
            Sys.sleep(0.5)
            message("Count ", count)
            paste(
                "Count: ", count, "\n", digest::sha1( c(input$A, S()) )
                )
    })

    # When M changes, we need to change S based on A and M
    # OR set S to a default value
    observeEvent(input$M, {
        
        # If user gets rid of M, reset S to default
        if(input$M == ""){
            S = "testS"
        }else{
            S = digest::sha1(c(input$M,input$A))
        }
        
        # Update the input to new S
        updateTextInput(
            session, 
            "S",
            value = S
        )
    })

    # When A changes, we need to change S based on A and M
    # OR if M is blank, do nothing (S doesn't depend on M if M is blank)
    observeEvent(input$A, {
    
        # If there's no M, don't use it
        if(input$M == "") return()
        
        # Update the input to new S
        updateTextInput(
            session, 
            "S",
            value = digest::sha1(c(input$M,input$A))
        )
    })
    
    # "Content"
    output$C <- renderText({
        C()
    })
    
}

shinyApp(ui = ui, server = server)

我认为这里的问题是你在应该使用反应式的时候使用了观察者。通常,您只想使用观察器来产生副作用(保存文件、按下按钮),而不是在您需要应用程序中的值时使用。在这里我认为最好使用 renderUI 来生成 UI 元素而不是用观察者更新它。

library(shiny)
library(digest)

ui <- fluidPage(
  
  sidebarLayout(
    sidebarPanel(
      textInput("M", "M:", ""), 
      uiOutput("S_UI"),
      selectInput("A", "A:", c("A1","A2"))
    ),
    
    mainPanel(
      verbatimTextOutput("C")
    )
  )
)

server <- function(input, output, session) {
  
  # Count calculations of C
  count = 0

  # Create "content" from A and S
  C <- reactive({
    count <<- count + 1
    Sys.sleep(0.5)
    message("Count ", count)
    paste(
      "Count: ", count, "\n", digest::sha1( c(input$A, input$S ))
    )
  })
  
  output$S_UI <- renderUI({
    if (input$M == "") {
      val <- "testS"
    } else {
      val <- "S"
    }
    return(textInput("S", "S:", val))
  })
  
  # "Content"
  output$C <- renderText({
    C()
  })
}

shinyApp(ui = ui, server = server)

这应该可行(如果我理解您的函数的逻辑)。它有一堆额外的消息,因此您可以查看更新的时间和顺序。

(我还更改了 count 的超赋值,因为那些只会让我烦恼,但我知道它对 isolate 有点尴尬,所以请随意放回去;)

library(shiny)
library(digest)

ui <- fluidPage(
  
  sidebarLayout(
    sidebarPanel(
      textInput("M", "M:", ""), 
      textInput("S", "S:", "testS"),
      selectInput("A", "A:", c("A1","A2"))
    ),
    
    mainPanel(
      verbatimTextOutput("C")
    )
  )
)

server <- function(input, output, session) {
  # Count calculations of C
  count = reactiveVal(0)
  S = reactiveVal("testS")
  
  observeEvent(input$S, { message("S updated externally")
    S(input$S)
  })
  
  # When M changes, we need to change S based on A and M
  # OR set S to a default value
  observeEvent(input$M, { message("M updated")
    # If user gets rid of M, reset S to default
    if (input$M == ""){
      S("testS")
    } else { 
      S(digest::sha1(c(input$M, input$A)))
    }
    
    # Update the input to new S 
    updateTextInput(inputId =  "S", value = S())
    message("S updated by M")
  })
  
  # When A changes, we need to change S based on A and M
  # OR if M is blank, do nothing (S doesn't depend on M if M is blank)
  observeEvent(input$A, { message("A updated")
    # If there's no M, don't use it
    req(input$M)
    
    # Update the input to new S
    S(digest::sha1(c(input$M, input$A)))
    updateTextInput(inputId =  "S", value = S())
    message("S updated by A")
  })
  
  # "Content"
  output$C <- renderText({ 
    n = isolate(count()) + 1
    count(n)
    #Sys.sleep(0.5)
    message("Count ", n)
    
    paste("Count: ", n, "\n", digest::sha1( c(input$A,  S())))
  })
}

shinyApp(ui = ui, server = server)

当用户通过 input$Minput$A 从外部或内部更改 input$S 时,S() reactiveVal 会更新。注释显示了哪些更改了值,output$C 仅在 input$AS() 更改时更改。