在 R 的 Shiny 应用程序中,如何延迟反应的触发?
In Shiny apps for R, how do I delay the firing of a reactive?
我的 Shiny
应用程序中有一个 selectizeInput
。它处于多select模式,因此用户可以指定多个select离子。
然而,依赖于 selectizeInput
的反应在每次添加 select 离子时都会被激发。假设用户打算 select A
、B
和 C
。目前,我的应用程序将为 select 离子 A
、A, B
和 A, B, C
进行昂贵的计算,而只需要最后一个。
我认为解决此问题的最佳方法是将 selectizeInput
的发射延迟一秒左右,让用户有机会输入所有 select 离子。每个新的 selection 应将计时器设置回 1 秒。我知道 Shiny
提供了一个 invalidateLater
命令,但这会导致反应式触发一次,稍后触发一次。
我怎样才能让 以后只触发一次反应?
你应该debounce反应。
这里有一个R实现:
https://gist.github.com/jcheng5/6141ea7066e62cafb31c
# Returns a reactive that debounces the given expression by the given time in
# milliseconds.
#
# This is not a true debounce in that it will not prevent \code{expr} from being
# called many times (in fact it may be called more times than usual), but
# rather, the reactive invalidation signal that is produced by expr is debounced
# instead. This means that this function should be used when \code{expr} is
# cheap but the things it will trigger (outputs and reactives that use
# \code{expr}) are expensive.
debounce <- function(expr, millis, env = parent.frame(), quoted = FALSE,
domain = getDefaultReactiveDomain()) {
force(millis)
f <- exprToFunction(expr, env, quoted)
label <- sprintf("debounce(%s)", paste(deparse(body(f)), collapse = "\n"))
v <- reactiveValues(
trigger = NULL,
when = NULL # the deadline for the timer to fire; NULL if not scheduled
)
# Responsible for tracking when f() changes.
observeEvent(f(), {
# The value changed. Start or reset the timer.
v$when <- Sys.time() + millis/1000
}, ignoreNULL = FALSE)
# This observer is the timer. It rests until v$when elapses, then touches
# v$trigger.
observe({
if (is.null(v$when))
return()
now <- Sys.time()
if (now >= v$when) {
v$trigger <- runif(1)
v$when <- NULL
} else {
invalidateLater((v$when - now) * 1000, domain)
}
})
# This is the actual reactive that is returned to the user. It returns the
# value of f(), but only invalidates/updates when v$trigger is touched.
eventReactive(v$trigger, {
f()
}, ignoreNULL = FALSE)
}
#' @examples
#' library(shiny)
#'
#' ui <- fluidPage(
#' numericInput("val", "Change this rapidly, then pause", 5),
#' textOutput("out")
#' )
#'
#' server <- function(input, output, session) {
#' debounced <- debounce(input$val, 1000)
#' output$out <- renderText(
#' debounced()
#' )
#' }
#'
#' shinyApp(ui, server)
我的 Shiny
应用程序中有一个 selectizeInput
。它处于多select模式,因此用户可以指定多个select离子。
然而,依赖于 selectizeInput
的反应在每次添加 select 离子时都会被激发。假设用户打算 select A
、B
和 C
。目前,我的应用程序将为 select 离子 A
、A, B
和 A, B, C
进行昂贵的计算,而只需要最后一个。
我认为解决此问题的最佳方法是将 selectizeInput
的发射延迟一秒左右,让用户有机会输入所有 select 离子。每个新的 selection 应将计时器设置回 1 秒。我知道 Shiny
提供了一个 invalidateLater
命令,但这会导致反应式触发一次,稍后触发一次。
我怎样才能让 以后只触发一次反应?
你应该debounce反应。
这里有一个R实现: https://gist.github.com/jcheng5/6141ea7066e62cafb31c
# Returns a reactive that debounces the given expression by the given time in
# milliseconds.
#
# This is not a true debounce in that it will not prevent \code{expr} from being
# called many times (in fact it may be called more times than usual), but
# rather, the reactive invalidation signal that is produced by expr is debounced
# instead. This means that this function should be used when \code{expr} is
# cheap but the things it will trigger (outputs and reactives that use
# \code{expr}) are expensive.
debounce <- function(expr, millis, env = parent.frame(), quoted = FALSE,
domain = getDefaultReactiveDomain()) {
force(millis)
f <- exprToFunction(expr, env, quoted)
label <- sprintf("debounce(%s)", paste(deparse(body(f)), collapse = "\n"))
v <- reactiveValues(
trigger = NULL,
when = NULL # the deadline for the timer to fire; NULL if not scheduled
)
# Responsible for tracking when f() changes.
observeEvent(f(), {
# The value changed. Start or reset the timer.
v$when <- Sys.time() + millis/1000
}, ignoreNULL = FALSE)
# This observer is the timer. It rests until v$when elapses, then touches
# v$trigger.
observe({
if (is.null(v$when))
return()
now <- Sys.time()
if (now >= v$when) {
v$trigger <- runif(1)
v$when <- NULL
} else {
invalidateLater((v$when - now) * 1000, domain)
}
})
# This is the actual reactive that is returned to the user. It returns the
# value of f(), but only invalidates/updates when v$trigger is touched.
eventReactive(v$trigger, {
f()
}, ignoreNULL = FALSE)
}
#' @examples
#' library(shiny)
#'
#' ui <- fluidPage(
#' numericInput("val", "Change this rapidly, then pause", 5),
#' textOutput("out")
#' )
#'
#' server <- function(input, output, session) {
#' debounced <- debounce(input$val, 1000)
#' output$out <- renderText(
#' debounced()
#' )
#' }
#'
#' shinyApp(ui, server)