Преимущества реактивного против наблюдения против наблюдения

Я прочитал все, что я могу о блестящем реактивном программировании. Я немного смущен. Следующие все работают, но какой метод предпочтительнее и почему? Очевидно, что приведенный ниже пример прост, но столкнусь ли я с проблемами при создании более крупного приложения любым из методов?

Я склоняюсь к стилю в коде сервера # 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)

Ответ 1

Во-первых, этот материал довольно двусмысленный, и в некотором смысле не очень интуитивный, об этом даже говорится в блоге Shiny!

Вот мое лучшее понимание темы..

Давайте начнем с reactive

Реактивная функция позволяет пользователю отслеживать состояние ввода или другой изменяющейся переменной и возвращать значение, которое будет использоваться в другом месте кода. Мониторинг реактивной переменной считается ленивым: "Реактивные выражения используют ленивую оценку; то есть, когда их зависимости меняются, они не сразу повторяются, а скорее ждут, пока их не вызовет кто-то другой. (Источник) ". Это хорошо показано в примере 2, так как вы можете вызвать переменную в среде renderText, после вызова кода внутри реактивного вызова выполняется и переоценивается переменная.

Для научных ботаников, это очень похоже на квантовую механику в том смысле, что, вызывая реактивную переменную (наблюдая за ней), заставляет ее измениться, переоценивая, слишком много натяжения?

Теперь 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)

enter image description here

Важно отметить, что во время выполнения кода в observe мы можем манипулировать реактивными переменными вне среды. В вашем случае вы назначаете text <- reactiveValues() а затем управляете этим, вызывая text$result <- 'Hi there'. Мы также можем делать такие вещи, как обновление выбора selectInput или другие блестящие виджеты, но мы не можем назначать никакие нереактивные переменные в этой среде, такие как наш getStatus в приведенном выше примере. И эта идея упоминается в документации observe,

"Наблюдатель подобен реактивному выражению в том, что он может считывать реактивные значения и вызывать реактивные выражения и автоматически повторяется при изменении этих зависимостей. Но в отличие от реактивных выражений, он не дает результата и не может использоваться как вход в другие реактивные выражения. Таким образом, наблюдатели полезны только для их побочных эффектов (например, выполнение ввода-вывода) (источник) "

Наконец, observeEvent

Лучший способ использовать observeEvent - это думать о нем как об определенном триггере, так как он наблюдает одно событие или изменение в переменной, а затем срабатывает, когда событие происходит. Я чаще всего использую это для наблюдения за вводом в кнопки, поскольку это определенное событие, в котором я хочу, чтобы что-то происходило после нажатия кнопки. Он использует isolate среду, которая, я думаю, является идеальным названием для работы этой функции.

Внутри этой среды мы можем назвать группу реактивных переменных, но мы определяем только одну как триггер. Основное различие между observeEvent и observe является триггером, так как observe запускается в любое время, когда что-либо меняется, и observeEvent ожидает триггера. Обратите внимание, что эта среда похожа на то, что она не возвращает нереактивные переменные.

Резюме

Вот пример, который объединяет все эти идеи:

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 Постоянно отслеживать реактивные события и переменные, всякий раз, когда ЛЮБАЯ реактивная переменная изменяется в среде (наблюдаемой среде), код оценивается. Может изменять значения ранее определенных реактивных переменных, не может создавать/возвращать переменные.

observeEvent (эффект домино) Постоянно отслеживайте ОДНУ определенную реактивную переменную/событие (триггер) и запускайте код, когда триггер активируется изменением/вводом этого триггера. Может изменять значения ранее определенных реактивных переменных, не может создавать/возвращать переменные.

eventReactive Создайте переменную с определенным триггером, аналогичным observeEvent. Используйте это, когда вы хотите реактивную переменную, которая оценивается из-за триггера, а не когда он вызывается.

Я надеюсь, что это поможет, и если я ошибаюсь в своем понимании или может быть больше разъяснений, не стесняйтесь редактировать этот ответ.

Ответ 2

Уже есть очень подробный ответ, поэтому я просто добавлю свои короткие короткие два цента:

По возможности, придерживайтесь reactive() а не reactiveValues(). Нормальный reactive() работает в соответствии с принципами блестящего реактивного программирования, а это означает, что выражение reactive() просто сообщает блестящему, как рассчитывается переменная, без указания когда. Shiny позаботится о том, чтобы определить, когда его рассчитать. Они будут оцениваться лениво (только при необходимости), они будут кешировать свои значения, они будут работать с функцией закладок - именно так, как блестящий был разработан и должен всегда быть первым выбором.

С reactiveValues() вы теперь снова находитесь на более императивной территории программирования, а не на реактивном. Существуют случаи, когда reactive() не обрезает его, и вам нужно использовать reactiveValues() (или reactiveVal()), но их следует использовать только в том случае, если reactive() не будет работать. Например, с reactive() есть только одно место, где переменная определена, поэтому, если вы хотите определить переменную в нескольких местах, вам нужно будет использовать reactiveValues(). Для более полного объяснения разницы между reactive() и reactiveValues(), вы можете увидеть мой ответ из старого поста

observe() и observeEvent(): вы можете думать о них как об одном и том же, но observeEvent() - это просто ярлык для observe() который вызывается определенными переменными, а остальная часть кода является isolate() -ed, На самом деле, все, что вы делаете с observeEvent() всегда можно сделать и с помощью observe(), это два варианта одного и того же.