web-dev-qa-db-fra.com

R Shiny: réactifValues ​​vs réactif

Cette question est liée à celui-ci . Les deux peuvent générer la même fonctionnalité, mais la mise en œuvre est légèrement différente. Une différence significative est qu'un reactiveValue est un conteneur qui peut avoir plusieurs valeurs, comme input$. Dans documentation brillante , les fonctionnalités sont généralement implémentées avec reactive(), mais dans la plupart des cas, je trouve reactiveValues() plus pratique. Y a-t-il un piège ici? Existe-t-il d'autres différences majeures entre les deux que je ne saurais peut-être pas remarquer? Ces deux extraits de code sont-ils équivalents?

Voir la même chose exemple de code implémenté en utilisant:

  1. une expression réactive:

    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. une valeur réactive:

    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)
    
48
Eduardo Bergel

Il y a un piège, bien qu'il ne vaille pas dans votre exemple.

Les développeurs brillants ont conçu reactive() pour être paresseux , ce qui signifie que l’expression qu’il contient ne sera exécutée que s’il est appelé par l’une des personnes à sa charge. Lorsque l'une de ses dépendances réactives est modifiée, il efface son cache et en informe ses propres dépendants, mais il n'est pas exécuté en tant que tel à la demande de l'un de ces dépendants. (Ainsi, si, par exemple, sa seule dépendante est un élément textOutput() sur un onglet masqué, il ne sera en réalité exécuté que si cet onglet est ouvert.)

observe(), d'autre part, est eager ; l'expression qu'elle contient sera exécutée immédiatement chaque fois qu'une de ses dépendances réactives est modifiée - même si sa valeur n'est pas utilisée par l'une de ses dépendantes (et même si a pas de dépendants ) Un tel empressement est souhaitable lorsque vous appelez observe() pour ses effets secondaires, mais cela peut être inutile lorsque vous n'êtes que en l'utilisant pour transmettre la valeur de retour de son contenu à d'autres expressions ou points de terminaison réactifs le long de la ligne.

Joe Cheng explique assez bien cette distinction dans son exposé de 2016 sur la "Programmation réactive efficace", disponible ici . Voir en particulier le peu à partir de 30h20 dans la deuxième heure de la présentation. Si vous regardez jusqu'à 40h42 (il clignote et vous le ratez!), Il caractérise brièvement le comportement de la combinaison observe()/reactiveValue () que vous aimez.

59
Josh O'Brien

Il est vrai que les deux concepts sont similaires et que vous pouvez souvent utiliser l'un ou l'autre pour résoudre votre problème. Mais il est généralement plus logique d’utiliser l’un ou l’autre.

Dans le cas de fibonacci, je pense que l’utilisation d’une expression reactive() a plus de sens, car currentFib est une valeur qui devrait être modifiée à des moments prévisibles très spécifiques (ie, quand input$n change, la valeur réactive doit être mise à jour en conséquence, ou réagit à ce changement).

Mais dans d'autres cas, il pourrait être plus simple et préférable d'utiliser reactiveValues. Je vais montrer deux exemples.

Tout d'abord, chaque fois que vous avez une variable que vous pensez avoir une sorte d'état (plutôt que de réagir à une valeur différente mise à jour), je pense que l'utilisation de reactiveValues est préférable.

Exemple:

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)

Dans le code ci-dessus, nous avons une variable total qui a l'état mutable, et il est beaucoup plus intuitif de la considérer comme une variable typique et de l'utiliser comme telle. C'est le cas le plus fréquent lorsque j'utilise reactiveValues.

J'ai aussi tilisez reactiveValues lorsqu'une variable peut être mise à jour à plusieurs endroits. Pour emprunter à l'exemple de fibonacci, considérons l'application brillante suivante, où le nombre n peut être défini par l'une des deux entrées suivantes:

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)

Cet exemple peut sembler un peu étrange dans le contexte de fibonacci, mais nous espérons que vous pourrez voir comment, dans d'autres applications complexes, vous souhaiterez parfois définir la valeur d'une variable à différents endroits. Il peut être plus intuitif de le faire à l'aide d'une reactiveValue plutôt qu'une expression réactive qui doit être implémentée dans un bloc.

J'espère que cela a été utile et logique. Bien sûr, ceci n’est que mon point de vue personnel sur le sujet. Ce n’est pas nécessairement ce que les brillants développeurs voulaient, mais c’est ainsi que j’ai appris à utiliser les deux méthodes.

35
DeanAttali