Shiny应用基础(6):数据响应的触发与阻止

1 数据响应

只要运行过shiny应用程序,你肯定已经看到shiny对用户数据的响应是“即时”的:只要数据发生改变,服务器就会重新处理这些数据并呈现新的结果。默认情况下,如果server端某个代码块包含有一些input,那么无论哪个input的值发生变化时这个代码块就会再运行一次。

这部分没用太多新的内容,我们补充说明一个函数:reactive。

前面我们使用的server端函数都是自动响应数据变化的函数。但那些都是“动作”类型的函数,如renderPlot进行绘图,renderText输出文本,observe监测ui端变化并响应。如果server端需要一个仅用于存储ui端数据的变量,该怎么做呢?很自然的想法是直接使用赋值语句:

shinyApp(
    ui = fixedPage(
        textInput('itx', '请随便输入'),
        textOutput('otx', container=pre)
    ),
    server = function(input, output, session) {
        itx <- input$itx  ## 直接使用赋值语句可以吗??
        output$otx <- renderPrint({
            itx
        })
    }
)


但上面的代码是不行的,shiny(至少现在的版本)不允许在规定的函数环境外面使用input数据,更不要说用这些变量存储数据供后面的代码使用。即使是这么简单的赋值,也需要使用规定的函数:reactive。把上面server中的代码改成下面代码就可以运行了:

itx <- reactive({input$itx})
output$otx <- renderPrint({
    itx()
})


reactive的返回值是一个结构structure,而R语言中结构是函数,所以得用 itx() 这样的函数形式来使用数据。


如果变量的值不使用input列表,这里有两种赋值方法:
server = function(input, output, session) {
    var1 <- list(a=1, b=2, c=3)
    var2 <- reactiveValues(a=1, b=2, c=3)
}
  • 第一方式直接赋值给var1,那么var1可以在server的其他环境(如observe)内使用,但不能修改/重新赋值,相当于常量,不可能触发数据响应
  • 第二种方式得到var2,它的值则可以在其他环境中修改,而且它的变化和input的变化一样,会触发数据响应。

这些处理方式有点让人费解,把简单的赋值操作搞得太复杂了点。

2 阻止响应

shiny对用户数据输入的自动响应虽然方便,但同时也带来不少问题:如果要调整很多参数但其实只需获取一次结果,会给服务器造成很大压力,无效运算非常多,客户端的响应也会变得非常慢。我们需要阻止一些数据输入触发程序响应,shiny提供的isolate函数有这样的作用。

isolate函数起到绝缘体的作用,它里面的input发生变化不会触发代码块的重新运行。运行下面程序有助于理解isolate的作用(shiny程序的运行方法不再重复,如不知道请参考第一节的内容):

shinyApp(
    ui = fixedPage(
        textInput('itx1', '', value='1111'),
        textInput('itx2', '', value='2222'),
        textOutput('otx', container=pre)
    ),
    server = function(input, output, session) {
        output$otx <- renderPrint({
            a <- NULL
            isolate(a <- input$itx1)
            b <- input$itx2
            list(a=a, b=b)
        })
    }
)
  • 代码块的运行(renderPrint)虽不响应isolate内部input的变化,但还会响应其他没用被isolate隔绝的input的变化
  • isolate内的代码不会被跳过,代码块运行一次它就运行一次(上面代码中a的最终结果永远不会是NULL)
  • isolate内的变量能够被isolate环境外的代码所使用

3 触发响应

理解了isolate的作用后要控制和触发shiny程序的响应是很容易的。留出一个input不isolate,就能大大减少不必要的数据响应。但更人性化的是设置一个响应按钮,由按钮触发响应。shiny对此有专门设定。

3.1 响应触发控件

shiny提供了两个专用控件函数:

## NOT RUN
actionButton(inputId, label, icon = NULL, ...)
actionLink(inputId, label, icon = NULL, ...)


除了产生的控件外观不一样外,两个函数的作用是一样的。它们的初始值是0,每按一次增加1。放在代码块中就可以由它们触发响应,很简单:

shinyApp(
    ui = fixedPage(
        textInput('itx', '', value=''),
        textOutput('otx', container=pre),
        actionButton('btn', '按钮', icon=icon('play-circle')),
        actionLink('lnk', '链接', icon=icon('play-circle'))
    ),
    server = function(input, output, session) {
        output$otx <- renderPrint({
            input$btn
            input$lnk
            isolate(input$itx)
        })
    }
)


3.2 集成环境

每个input都使用isolate来阻止响应是很繁琐的,稍不留神就可能遗忘。为方便应用,shiny开发者提供了两个函数。一个是事件类型的函数observeEvent,另一个eventReactive用于获取变量,分别对应observe和reactive函数。(类似功能的两个函数为什么命名规则不一致?怪异。)函数产生很多,但只需要设置前二:

## NOT RUN
observeEvent(eventExpr, handlerExpr, ...)
eventReactive(eventExpr, valueExpr, ...)


第一个参数一般使用button或link类input,但可用其他变量。第二个参数是表达式集合,多表达式用花括号隔离。第一个参数(即input)的值发生变化时运行第二个参数内的表达式。observeEvent不返回值,eventReactive有返回值。比如:

observeEvent(input$btn, {
                 output$otx <- renderPrint({
                     input$itx
                 })
             })


是不是很怪?正常一点的代码把数据和事件分开写:

dt <- eventReactive(input$btn, input$itx)
output$otx <- renderPrint({
    dt()
})


注意:eventReactive的返回值还是结构,是函数。

运行错误?OMG,为什么每次我都把 eventReactive 写成 reactiveEvent ?



作者: ZGUANG@LZU

Created: 2015-09-03 四 19:29

Emacs 24.4.1 (Org mode 8.2.10)

  • 10
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值