R-Shiny 响应式编程(一)

房东的猫 提交于 2019-12-26 14:46:35

Build Reactivity

Overview

reactivity-overview

在Shiny中,响应式编程中包含三种对象,这些对象用以下符号表示:

Reactive roles

  • reactive source & endpoint:最简单的响应式编程结构,只包括一个源点和一个终点。

在Shiny应用程序中,the source通常是通过浏览器界面输入的用户。 例如,当用户选择一个项目,键入输入或单击按钮时,这些操作将设置作为reactive source的值。 reactive endpoint通常是出现在用户浏览器窗口中的东西,例如图形或值表。

在一个简单的Shiny应用程序中,可通过输入对象可访问reactive source,并通过输出对象可访问reactive endpoint。 (实际上,还有其他可能的source和endpoint,我们将在后面讨论,但现在我们仅讨论输入和输出。)

01_hello示例使用具有一个source和一个endpoint的这种简单结构。 该示例的服务器功能代码如下所示:

server <- function(input, output) {
  output$distPlot <- renderPlot({
    hist(rnorm(input$obs))
  })
}

output$distPlot对象是reactive endpoint,它使用reactive source (input$obs)。 每当input$obs更改时,就会通知output$distPlot它需要重新执行。 在具有响应式用户界面的传统程序中,这可能涉及设置事件处理程序以及编写代码以读取值和传输数据。 Shiny在幕后为您完成所有这些操作,因此您只需编写看起来像常规R代码的代码即可。

一个reactive source可以连接到多个endpoint,反之亦然。 这是稍微复杂一些的Shiny应用程序的服务器功能:

server <- function(input, output) {
  output$plotOut <- renderPlot({
    hist(faithful$eruptions, breaks = as.numeric(input$nBreaks))
    if (input$individualObs)
      rug(faithful$eruptions)
  })

  output$tableOut <- renderTable({
    if (input$individualObs)
      faithful
    else
      NULL
  })
}

Structure of Old Faithful example

在Shiny应用程序中,无需明确描述这些关系中的每个关系,而无需告诉R当每个输入组件发生变化时该怎么做; Shiny会自动为您处理这些详细信息。

在具有上述结构的应用程序中,只要input$nBreaks的值发生更改,生成图的表达式将自动重新执行。 每当input$individualObs的值更改时,绘图和表格函数将自动重新执行。 (在Shiny应用程序中,大多数终结点函数的结果都会自动打包并发送到Web浏览器。)

  • reactive conductors

到目前为止,我们已经看到了reactive sourcereactive endpoint,大多数简单示例仅使用这两个组件,将source直接连接到endpoint。也可以在source和endpoint之间放置电抗组件。这些组件称为reactive conductors

reactive conductors既可以是依存关系,也可以有依存关系。换句话说,在响应式结构图中,它既可以是父代,也可以是子代。在响应式图中,source只能是父代(他们可以有依存关系),endpoint只能是子代(他们是依存关系)。

reactive conductors对于慢封装或高开销的计算操作可能很有用。例如,假设您有一个应用程序,该应用程序使用值input$n并在Fibonacci序列中打印_n_th值,并在序列中将_n_th的值反加一(请注意,这些示例中的代码已精简以说明反应性概念,不一定代表编码最佳实践):

# Calculate nth number in Fibonacci sequence
fib <- function(n) ifelse(n<3, 1, fib(n-1)+fib(n-2))

server <- function(input, output) {
  output$nthValue    <- renderText({ fib(as.numeric(input$n)) })
  output$nthValueInv <- renderText({ 1 / fib(as.numeric(input$n)) })
}

该app的结构图如下所示:

Fibonacci app without conductor

fib()算法的效率很低,除非必要,我们不希望花费太长的时间来运行它。 但是在这个应用程序中,我们运行了两次! 在相当快的现代计算机上,将input$n设置为30大约需要15秒才能计算出答案,这主要是因为fib()运行了两次。

可以通过在source和endpoint之间添加reactive conductor来减少计算量:

fib <- function(n) ifelse(n<3, 1, fib(n-1)+fib(n-2))

server <- function(input, output) {
  currentFib         <- reactive({ fib(as.numeric(input$n)) })

  output$nthValue    <- renderText({ currentFib() })
  output$nthValueInv <- renderText({ 1 / currentFib() })
}

新的结构图如下:

Fibonacci app with conductor

请记住,如果您的应用程序尝试在响应式上下文之外(即,在reactive expression或observer之外)访问响应式值或表达式,则将导致错误。 您可以想象存在一个可以看到和改变非响应式地盘的响应式“地盘”,但是非响应式地盘不能对响应式地盘做同样的事情。 像这样的代码将不起作用,因为对fib()的调用不在响应式地盘中(不在active()或renderXX()调用中),但是它尝试访问的是响应式值input$n:

server <- function(input, output) {
  # Will give error
  currentFib      <- fib(as.numeric(input$n))
  output$nthValue <- renderText({ currentFib })
}

举个不恰当的例子,可能就像是C语言中不能int n; scanf("%d", &n); int a[n];?

另一方面,如果currentFib是访问非响应式的函数,并且该函数在非响应式地盘中被调用,则它将起作用:

server <- function(input, output) {
  # OK, as long as this is called from the reactive world:
  currentFib <- function() {
    fib(as.numeric(input$n))
  }

  output$nthValue <- renderText({ currentFib() })
}

相对的,如果是定义了const int n = 1000;就可以了?

总结:

在本节中,我们了解到:

  • reactive source可以向下游示意它们需要重新执行。
  • Reactive conductors放置在响应式图上sources和endpoints之间的某个位置。 它们通常用于封装慢操作。
  • Reactive endpoints可以被Reactive enviornment重新执行,并且可以请求上游对象执行。
  • 无效箭头(Invalidation arrows)表示无效事件的流程。 也可以说子节点是父节点的依赖,或与父节点之间有依赖(child node is a dependent of or takes a dependency on the parent node)。

Implementations of sources, conductors, and endpoints: values, expressions, and observers

我们已经讨论了reactive sources,conductors和endpoints。 这些是在响应式程序中扮演特定角色的零件的通用术语。 当前,Shiny具有一类充当reactive sources的对象,一类充当reactive conductors的对象以及一类充当reactive endpoints的对象,但是原则上可以有其他类别实现这些角色。

  • Reactive values are an implementation of Reactive sources; that is, they are an implementation of that role. 响应式值是响应式源的实现;它们是reactive sources的实现。
  • Reactive expressions are an implementation of Reactive conductors. They can access reactive values or other reactive expressions, and they return a value. 响应式表达式是Reactive conductors的一种实现,他们可以访问响应式值或其他响应式表达式,并返回一个值。
  • Observers are an implementation of Reactive endpoints. They can access reactive sources and reactive expressions, and they don’t return a value; they are used for their side effects. 观察者是Reactive endpoints的实现。 他们可以访问响应式源和响应式表达式,并且不返回值; 

Implementations of reactive roles

所有示例均使用这三种实现,因为目前没有其他sources,conductors和endpoints角色的实现。

Reactive values

响应式值包含值(毫不奇怪),其他响应式对象可以读取该值。 输入对象是一个ReactiveValues对象,它看起来像一个列表,并且包含许多单独的响应式值。 输入中的值是通过Web浏览器的输入设置的。

Reactive expressions

通过上面的斐波那契示例,我们已经看到了响应式表达式在起作用。 他们缓存其返回值,以使应用程序更有效地运行。 请注意,抽象地说,reactive conductors不一定要缓存返回值,但是在此实现中,reactive conductors确实可以。

响应式表达式可用于缓存响应用户输入而发生的任何过程的结果,包括:

  • 访问数据库
  • 从文件读取数据
  • 通过网络下载数据
  • 执行高开销的计算

Observers

观察者类似于响应式表达,但有一些重要的区别。 与响应式一样,它们可以访问响应式值和响应式表达式。 但是,它们不返回任何值,因此不缓存其返回值。 它们没有返回值,而是有副作用–通常,这涉及将数据发送到Web浏览器。

输出对象看起来像一个列表,它可以包含许多单独的观察者。

如果您看一下renderText()的代码,朋友,你会发现他们每个都返回一个函数,该函数返回一个值。 通常按以下方式使用它们:

output$number <- renderText({ as.numeric(input$n) + 1 })

Differences between reactive expressions and observers

响应式表达式和观察者的相似之处在于它们存储可以执行的表达式,但是它们之间存在一些根本差异。

  • 观察者(通常是endpoint)对响应式刷新事件做出响应,而响应式表达式(通常是conductors)则不响应。 我们将在下一部分中详细了解刷新事件。 如果要执行反应式表达式,则它必须具有观察者作为响应式依赖图上的子代。
  • 响应式表达式返回值,但观察者则不。

 

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!