你怎么能接近reactive environment/behavior函数建立的shiny,或者甚至可以在 non-shiny 上下文中使用这些函数来创建"反应性"变量
我对shiny framework及其基本范例非常着迷。特别是关于已建立的整体反应环境。只是为了它的纯粹乐趣,我想知道是否可以将这种反应式编程范式转换为非闪亮的上下文 - 即常规的R应用程序/项目/包或者你想要调用它。
也许会想到选项:您可能希望option_2
依赖option_1
的值来确保
一致的数据状态。如果option_1
发生变化,option_2
也会发生变化。
我想我想找到尽可能高效的内容,例如option_2
只应在必要时更新,即当option_1
实际发生变化时(如反对计算当前状态option_2
每次我查询选项。)
我使用以下功能玩了一下:
shiny::reactiveValues
shiny::reactive
shiny::observe
shiny::isolate
但是AFAIU,当然,它们与闪亮的环境紧密相关。
这是一个基于environment
的非常简单的解决方案。它有效,但
set
函数的定义:
setValue <- function(
id,
value,
envir,
observe = NULL,
binding = NULL,
...
) {
## Auxiliary environments //
if (!exists(".bindings", envir, inherits = FALSE)) {
assign(".bindings", new.env(), envir)
}
if (!exists(".hash", envir, inherits = FALSE)) {
assign(".hash", new.env(), envir)
}
if (!exists(".observe", envir, inherits = FALSE)) {
assign(".observe", new.env(), envir)
}
if (!exists(id, envir$.hash, inherits = FALSE)) {
assign(id, new.env(), envir$.hash)
}
## Decide what type of variable we have //
if (!is.null(observe) && !is.null(binding)) {
has_binding <- TRUE
} else {
has_binding <- FALSE
}
## Set //
if (has_binding) {
## Value with binding //
## Get and transfer hash value of observed variable:
assign(id, get(observe, envir$.hash[[observe]]), envir$.hash[[observe]])
## Compute actual value based on the binding contract/function:
out <- binding(x = get(observe, envir))
## Store actual value:
assign(id, out, envir)
## Store hash value:
assign(id, digest::digest(out), envir$.hash[[id]])
## Store binding:
assign(id, binding, envir$.bindings)
## Store name of observed variable:
assign(id, observe, envir$.observe)
} else {
## Regular variable without binding //
## Store actual value:
out <- assign(id, value, envir)
## Store hash value:
assign(id, digest::digest(value), envir$.hash[[id]])
}
return(out)
}
get
函数的定义:
getValue <- function(
id,
envir,
...
) {
## Check if variable observes another variable //
observe <- envir$.observe[[id]]
## Get //
if (!is.null(observe)) {
## Check if any of observed variables have changed //
## Note: currently only tested with bindings that only
## take one observed variable
idx <- sapply(observe, function(ii) {
hash_0 <- get(ii, envir$.hash[[ii]], inherits = FALSE)
hash_1 <- get(id, envir$.hash[[ii]], inherits = FALSE)
hash_0 != hash_1
})
## Update required //
if (any(idx)) {
out <- setValue(
id = id,
envir = envir,
binding = get(id, envir$.bindings, inherits = FALSE),
observe = observe
)
} else {
out <- get(id, envir, inherits = FALSE)
}
} else {
out <- get(id, envir, inherits = FALSE)
}
return(out)
}
应用
##------------------------------------------------------------------------------
## Apply //
##------------------------------------------------------------------------------
require("digest")
envir <- new.env()
## Set regular variable value //
setValue(id = "x_1", value = Sys.time(), envir = envir)
[1] "2014-09-17 23:15:38 CEST"
getValue(id = "x_1", envir = envir)
# [1] "2014-09-17 23:15:38 CEST"
## Set variable with binding to observed variable 'x_1' //
setValue(
id = "x_2",
envir = envir,
binding = function(x) {
x + 60*60*24
},
observe = "x_1"
)
# [1] "2014-09-18 23:15:38 CEST"
## As long as observed variable does not change,
## value of 'x_2' will also not change
getValue(id = "x_2", envir = envir)
# [1] "2014-09-18 23:15:38 CEST"
## Change value of observed variable 'x_1' //
setValue(id = "x_1", value = Sys.time(), envir = envir)
# [1] "2014-09-17 23:16:52 CEST"
## Value of 'x_2' will change according to binding contract/function:
getValue(id = "x_2", envir = envir)
# [1] "2014-09-18 23:16:52 CEST"
监测:
##------------------------------------------------------------------------------
## Profiling //
##------------------------------------------------------------------------------
require(microbenchmark)
envir <- new.env()
binding <- function(x) {
x + 60*60*24
}
microbenchmark(
"1" = setValue(id = "x_1", value = Sys.time(), envir = envir),
"2" = getValue(id = "x_1", envir = envir),
"3" = setValue(id = "x_2", envir = envir,
binding = binding, observe = "x_1"),
"4" = getValue(id = "x_2", envir = envir),
"5" = setValue(id = "x_1", value = Sys.time(), envir = envir),
"6" = getValue(id = "x_2", envir = envir)
)
# Unit: microseconds
# expr min lq median uq max neval
# 1 108.620 111.8275 115.4620 130.2155 1294.881 100
# 2 4.704 6.4150 6.8425 7.2710 17.106 100
# 3 178.324 183.6705 188.5880 247.1735 385.300 100
# 4 43.620 49.3925 54.0965 92.7975 448.591 100
# 5 109.047 112.0415 114.1800 159.2945 223.654 100
# 6 43.620 47.6815 50.8895 100.9225 445.169 100
答案 0 :(得分:6)
对于那些感兴趣的人:这周末一直困扰着我,所以我把一个名为reactr的小包装在一起,这个包基于可以通过makeActiveBinding
定义绑定的方式。您可以找到基本构思here。
where
和where_watch
)。 binding_type = 2
获得。但是,它不支持使用assign()
和get()
(<-
和<obj-name>
或$<obj-name>
)的语法糖来保持哈希值值同步 - 所以我猜不会使用它。我真正喜欢的是我需要一个辅助环境来存储被比较的哈希值,以便做出决定&#34;更新缓存或返回缓存&#34;。它在where
中浮动,默认情况下位于where$._HASH
(请参阅ensureHashRegistryState()
,但至少可以将名称/ ID更改为您更喜欢或需要的名称/ ID(请参阅参数{{1 }})。
如果有人知道如何摆脱这种情况,那就非常感激! : - )强>
请参阅README.md
负载:
.hash_id
使用示例环境,这样我们就不会搞砸我们的require("devtools")
devtools::install_github("Rappster/classr")
devtools::install_github("Rappster/reactr")
require("reactr")
:
.GlobalEnv
设置可监控的变量:
where <- new.env()
设置一个监视setReactive(id = "x_1", value = 10, where = where)
并且具有反应绑定的变量:
x_1
每当setReactiveid = "x_2", watch = "x_1", where = where)
发生变化时,x_1
会相应更改:
x_2
请注意,尝试更改where$x_1
# [1] 10
where$x_2
# [1] 10
where$x_1 <- 100
where$x_2
# [1] 100
时会被忽略,因为它只能监控x_2
:
x_1
where$x_2 <- 1000
where$x_2
# [1] 100
每当setReactiveid = "x_3", watch = "x_1", where = where, binding = function(x) {x * 2})
发生变化时,x_1
会相应更改:
x_3
设置两个具有相互绑定的变量。
与绑定方案1 的主要区别在于,您可以设置
where$x_1
# [1] 100
where$x_2
# [1] 100
where$x_3
# [1] 200
where$x_1 <- 500
where$x_2
# [1] 500
where$x_3
# [1] 1000
和 x_1
,并反映更改。
为了做到这一点,还需要重置x_4
的绑定
与x_1
:
mutual = TRUE
每当setReactive(id = "x_1", watch = "x_4", where = where, mutual = TRUE)
setReactive(id = "x_4", watch = "x_1", where = where, mutual = TRUE)
更改时,x_1
会相应更改,反之亦然。
请注意,具有相互绑定的变量仅由x_4
初始化,默认值为setThis
。您必须实际为一个值分配值
在建立绑定之后通过NULL
进行的操作:
<-
where$x_1
# NULL
where$x_4
# NULL
where$x_1 <- 100
where$x_1
# [1] 100
where$x_4
# [1] 100
where$x_2
# [1] 100
where$x_3
# [1] 200
where$x_4 <- 1000
where$x_4
# [1] 1000
where$x_1
# [1] 1000
where$x_2
# [1] 1000
where$x_3
# [1] 2000
请参阅setReactive(id = "x_5", watch = "x_6", where = where,
binding = function(x) {x * 2}, mutual = TRUE)
setReactive(id = "x_6", watch = "x_5", where = where,
binding = function(x) {x / 2}, mutual = TRUE)
where$x_5 <- 100
where$x_5
# [1] 100
where$x_6
# [1] 50
where$x_6 <- 500
where$x_6
# [1] 500
where$x_5
# [1] 1000
和?setReactive
。
我在/inst/prof/prof_1.r
中添加了一个分析脚本。有一个&#34;裸&#34; S3方法?setReactive_bare
大约快10%。
使用S4方法setThis_bare
setValue()
使用S3函数where <- new.env()
res_1 <- microbenchmark(
"1" = setReactive(id = "x_1", value = 10, where = where),
"2" = getReactive(id = "x_1", where = where),
"3" = setReactive(id = "x_2", where = where, watch = "x_1",
binding = function(x) {x + 100}),
"4" = getReactive(id = "x_2", where = where),
"5" = setReactive(id = "x_1", value = 100, where = where),
"6" = getReactive(id = "x_2", where = where),
control = list(order = "inorder")
)
Unit: microseconds
expr min lq median uq max neval
1 476.387 487.9330 494.7750 545.6640 7759.026 100
2 25.658 26.9420 27.5835 30.5770 55.166 100
3 644.875 657.7045 668.1820 743.6595 7343.364 100
4 34.211 35.4950 36.3495 38.4870 86.384 100
5 482.802 494.7750 505.4665 543.9535 2665.027 100
6 51.744 53.0280 54.3100 58.1595 99.640 100
setThis_bare()
这是样板代码在where <- new.env()
res_3 <- microbenchmark(
"1" = setReactive_bare(id = "x_1", value = 10, where = where),
"2" = getReactive(id = "x_1", where = where),
"3" = setReactive_bare(id = "x_2", where = where, watch = "x_1",
binding = function(x) {x + 100}),
"4" = getReactive(id = "x_2", where = where),
"5" = setReactive_bare(id = "x_1", value = 100, where = where),
"6" = getReactive(id = "x_2", where = where),
control = list(order = "inorder")
)
Unit: microseconds
expr min lq median uq max neval
1 428.492 441.9625 453.936 567.4735 6013.844 100
2 25.659 26.9420 27.797 33.9980 84.672 100
3 599.546 613.0165 622.852 703.0340 2369.103 100
4 34.211 35.9220 36.777 45.5445 71.844 100
5 436.189 448.1630 457.571 518.5095 2309.662 100
6 51.745 53.4550 54.952 60.5115 1131.952 100
内传递给makeActiveBinding()
的样子(省略setThis()
内容;请参阅/R/getBoilerplateCode.r
)。
可以监控的变量:
message()
准备评估:
out <- substitute(
local({
VALUE <- NULL
function(v) {
if (!missing(v)) {
VALUE <<- v
## Ensure hash value //
assign(id, digest::digest(VALUE), where[[HASH]][[id]])
}
VALUE
}
}),
list(
VALUE = as.name("value"),
HASH = as.name(".hash_id")
)
)
监控的变量:
getBoilerplateCode(
ns = classr::createInstance(cl = "Reactr.BindingContractMonitored.S3")
)
准备评估:
out <- substitute(
local({
if ( exists(watch, envir = where_watch, inherits = FALSE) &&
!is.null(get(watch, envir = where_watch, inherits = FALSE))
) {
VALUE <- BINDING_CONTRACT
} else {
VALUE <- NULL
}
function(v) {
if (exists(watch, envir = where_watch, inherits = FALSE)) {
if (missing(v)) {
hash_0 <- where_watch[[HASH]][[watch]][[watch]]
hash_1 <- where_watch[[HASH]][[watch]][[id]]
if (hash_0 != hash_1) {
VALUE <<- BINDING_CONTRACT
where_watch[[HASH]][[watch]][[id]] <- hash_0
where[[HASH]][[id]][[id]] <- hash_0
where[[HASH]][[id]][[watch]] <- hash_0
}
}
}
VALUE
}
}),
list(
VALUE = as.name("value"),
BINDING_CONTRACT = substitute(.binding(x = where_watch[[watch]])),
HASH = as.name(".hash_id")
)
)
具有相互绑定的变量:
getBoilerplateCode(
ns = classr::createInstance(cl = "Reactr.BindingContractMonitoring.S3")
)
准备评估:
out <- substitute(
local({
if ( exists(watch, envir = where, inherits = FALSE) &&
!is.null(get(watch, envir = where, inherits = FALSE))
) {
VALUE <- BINDING_CONTRACT
} else {
VALUE <- NULL
}
function(v) {
if (!missing(v)) {
VALUE <<- v
## Update hash value //
assign(id, digest::digest(VALUE), where[[HASH]][[id]])
}
if (exists(watch, envir = where, inherits = FALSE)) {
if (missing(v)) {
hash_0 <- where[[HASH]][[watch]][[watch]]
hash_1 <- where[[HASH]][[watch]][[id]]
if (hash_0 != hash_1) {
VALUE <<- BINDING_CONTRACT
where[[HASH]][[watch]][[id]] <- hash_0
where[[HASH]][[id]][[id]] <- hash_0
where[[HASH]][[id]][[watch]] <- hash_0
}
}
}
VALUE
}
}),
list(
VALUE = as.name("value"),
BINDING_CONTRACT = substitute(.binding(x = where[[watch]])),
HASH = as.name(".hash_id")
)
)
答案 1 :(得分:3)
(试图将此作为评论,但S.O.说它太长了。)
赞赏更加密切关注反应性。您可能会发现这两个链接很有用:
所以实际上Shiny的反应性可以在Shiny应用程序之外使用 - 有两个技巧。
options(shiny.suppressMissingContextError=TRUE)
以使其消失。shiny:::flushReact()
之前,观察员实际上并未执行。这样您就可以执行多个更新,然后让所有响应代码响应一次,而不是每次更新时重新计算。对于控制台使用,您可以要求Shiny使用flushReact
在每个控制台提示符上自动调用shiny:::setAutoflush(TRUE)
。同样,只有观察者才需要这样做。今天有效的示例(在控制台上逐行执行):
library(shiny)
options(shiny.suppressMissingContextError=TRUE)
makeReactiveBinding("x_1")
x_1 <- Sys.time()
x_2 <- reactive(x_1 + 60*60*24)
x_1
x_2()
x_1 <- Sys.time()
x_1
x_2()
# Now let's try an observer
shiny:::setAutoflush(TRUE)
observe(print(paste("The time changed:", x_1)))
x_1 <- Sys.time()
我建议再考虑更直接地利用Shiny的反应抽象。我认为你可以用makeActiveBinding
非常直接地实现这样的语法(假设你认为这比Shiny今天给你的更好):
where <- new.reactr()
where$x_1 <- Sys.time()
where$x_2 <- reactive(x_1 + 60*60*24)
where$x_1 # Read x_1
where$x_2 # Read x_2
使用reactive()
而不是setThis
声明反应式表达式的一个关键优势是,前者可以轻松自然地模拟依赖于多个反应值/表达式的表达式。请注意,反应式表达式都是缓存的和惰性的:如果您修改x_1
,则在尝试阅读x_2
之前,它实际上不会重新计算x_2
,如果您再次阅读x_2
x_1
已更改,然后只返回上一个值而不重新计算。
关于Shiny反应性的更多功能性转变,请参阅受https://github.com/hadley/shinySignals启发的Hadley Wickham的新包Elm。
希望有所帮助。
答案 2 :(得分:3)
位置test_that
中有一组/usr/local/lib/R/site-library/shiny/tests/
单元测试。它们让你很好地了解函数/包装器:
reactiveValues
reactive
observe
isolate
可以在shinyServer
来电之外使用。
关键是使用flushReact
来消除反应性。例如,这是文件test-reactivity.r
中的一个测试,我认为它已经让您很好地了解了您需要做什么:
test_that("overreactivity2", {
# ----------------------------------------------
# Test 1
# B depends on A, and observer depends on A and B. The observer uses A and
# B, in that order.
# This is to store the value from observe()
observed_value1 <- NA
observed_value2 <- NA
values <- reactiveValues(A=1)
funcB <- reactive({
values$A + 5
})
obsC <- observe({
observed_value1 <<- funcB() * values$A
})
obsD <- observe({
observed_value2 <<- funcB() * values$A
})
flushReact()
expect_equal(observed_value1, 6) # Should be 1 * (1 + 5) = 6
expect_equal(observed_value2, 6) # Should be 1 * (1 + 5) = 6
expect_equal(execCount(funcB), 1)
expect_equal(execCount(obsC), 1)
expect_equal(execCount(obsD), 1)
values$A <- 2
flushReact()
expect_equal(observed_value1, 14) # Should be 2 * (2 + 5) = 14
expect_equal(observed_value2, 14) # Should be 2 * (2 + 5) = 14
expect_equal(execCount(funcB), 2)
expect_equal(execCount(obsC), 2)
expect_equal(execCount(obsD), 2)
})
答案 3 :(得分:1)
感谢Rappster,Joe和Robert,你的谈话确实让我受益匪浅。
我刚刚编写了一个小工具,使用以下想法构建可缓存的函数:
library(shiny)
gen.f <- function () {
reactv <- reactiveValues()
a <- reactive({ print('getting a()'); reactv$x + 1 })
b <- reactive({ print('getting b()'); reactv$y + 1 })
c <- reactive({ print('getting c()'); a() + b() })
function (x.value, y.value) {
reactv$x <<- x.value
reactv$y <<- y.value
isolate(c())
}
}
f <- gen.f()
在上面的例子中,返回函数的父环境 用于存储反应值和反应表达式。
通过这样做,返回的函数将具有缓存它的能力 中间结果,如果函数不需要重新计算它们 进一步调用相同的参数。底层的反应式表达式包含在内部,函数可以 用作普通的R函数。
> f(6,9)
[1] "getting c()"
[1] "getting a()"
[1] "getting b()"
[1] 17
> f(6,9)
[1] 17
> f(6,7)
[1] "getting c()"
[1] "getting b()"
[1] 15
基于这个想法,我写了一个工具来帮助生成这种可缓存的 具有以下语法的函数。你可以在https://github.com/marlin-na/reactFunc
看到我的回购myfunc <- reactFunc(
# ARGV is the formal arguments of the returned function
ARGV = alist(x = , y = ),
# These are reactive expressions in the function argument form
a = { print('getting a()'); x + 1 },
b = { print('getting b()'); y + 1 },
ans = { print('getting ans()'); a() + b() }
)
> myfunc(6, 9)
[1] "getting ans()"
[1] "getting a()"
[1] "getting b()"
[1] 17
> myfunc(6, 9)
[1] 17
> myfunc(6, 7)
[1] "getting ans()"
[1] "getting b()"
[1] 15
此致
米;
答案 4 :(得分:0)
感谢Joe的指示,我能够显着简化设计。我真的很喜欢不需要担心一些变量是否是一个反应变量(前者暗示你必须通过{{1}执行底层反应绑定函数在Joe的回答中()
中就像在{}}中那样。这就是为什么我尝试将Joe的代码与x_2()
结合起来。
makeActiveBinding()
,实际的反应性细节留给where$._HASH
- 这真是太棒了,因为如果有人知道如何掌握R中的反应性& #39;可能是RStudio的人;-)而且,这样整个事情甚至可能与shiny
应用兼容 - 好吧,至少在理论上; - )shiny
并不关心您向其提供的观察变量的数量 - 只要它们处于相同的环境中(reactive()
中的env
{ar} reactive()
,arg where
在我的代码中)。 reactive()
的返回值非常棘手,因为它建议的对象比实际返回的更简单(参考类)。这使得很难与substitute()
&#34;以及#34;因为这会导致一些静态绑定(适用于第一个周期,但它是静态的)。
我需要使用旧的解决方法,一直回到将整个事物转换为character
字符串:
reactive_expr <- gsub(") $", ", env = where)", capture.output(reactive(x_1 + 60*60*24))
可能有点危险或不可靠,但似乎capture.output(reactive())
的结尾总是有尾随的空白,这对我们来说是个好消息,因为它让我们识别出最后的)
。 / p>
另外,这也带有一种 Pro :当where
添加 setReactive
时,用户不需要指定where
两次 - 否则将需要:
where <- new.env()
setReactive("x_1", reactive(x_2 + 60*60*24, env = where), where = where)
require("shiny")
setReactive <- function(
id = id,
value = NULL,
where = .GlobalEnv,
.tracelevel = 0,
...
) {
## Ensure shiny let's me do this //
shiny_opt <- getOption("shiny.suppressMissingContextError")
if (is.null(shiny_opt) || !shiny_opt) {
options(shiny.suppressMissingContextError = TRUE)
}
## Check if regular value assignment or reactive function //
if (!inherits(value, "reactive")) {
is_reactive <- FALSE
shiny::makeReactiveBinding(symbol = id, env = where)
value_expr <- substitute(VALUE, list(VALUE = value))
} else {
is_reactive <- TRUE
## Put together the "line of lines" //
value_expr <- substitute(value <<- VALUE(), list(VALUE = value))
## --> works initially but seems to be static
## --> seems like the call to 'local()' needs to contain the *actual*
## "literate" version of 'reactive(...)'. Evaluationg it
## results in the reactive object "behind" 'reactive(()' to be assigned
## and that seems to make it static.
## Workaround based character strings and re-parsing //
reactive_expr <- gsub(") $", ", env = where)", capture.output(value))
value_expr <- substitute(value <<- eval(VALUE)(),
list(VALUE = parse(text = reactive_expr)))
}
## Call to 'makeActiveBinding' //
expr <- substitute(
makeActiveBinding(
id,
local({
value <- VALUE
function(v) {
if (!missing(v)) {
value <<- v
} else {
VALUE_EXPR
}
value
}
}),
env = where
),
list(
VALUE = value,
VALUE_EXPR = value_expr
)
)
if (.tracelevel == 1) {
print(expr)
}
eval(expr)
## Return value //
if (is_reactive) {
out <- get(id, envir = where, inherits = FALSE)
} else {
out <- value
}
return(out)
}
## In .GlobalEnv //
## Make sure 'x_1' and 'x_2' are removed:
suppressWarnings(rm(x_1))
suppressWarnings(rm(x_2))
setReactive("x_1", value = Sys.time())
x_1
# [1] "2014-09-24 18:35:49 CEST"
x_1 <- Sys.time()
x_1
# [1] "2014-09-24 18:35:51 CEST"
setReactive("x_2", value = reactive(x_1 + 60*60*24))
x_2
# [1] "2014-09-25 18:35:51 CEST"
x_1 <- Sys.time()
x_1
# [1] "2014-09-24 18:36:47 CEST"
x_2
# [1] "2014-09-25 18:36:47 CEST"
setReactive("x_3", value = reactive({
message(x_1)
message(x_2)
out <- x_2 + 60*60*24
message(paste0("Difference: ", out - x_1))
out
}))
x_3
# 2014-09-24 18:36:47
# 2014-09-25 18:36:47
# Difference: 2
# [1] "2014-09-26 18:36:47 CEST"
x_1 <- Sys.time()
x_1
# [1] "2014-09-24 18:38:50 CEST"
x_2
# [1] "2014-09-25 18:38:50 CEST"
x_3
# 2014-09-24 18:38:50
# 2014-09-25 18:38:50
# Difference: 2
# [1] "2014-09-26 18:38:50 CEST"
## Setting an observer has no effect
x_2 <- 100
x_2
# [1] "2014-09-25 18:38:50 CEST"
类似于使用.GlobalEnv
,除了您需要声明/使用where
之外:
where <- new.env()
suppressWarnings(rm(x_1, envir = where))
suppressWarnings(rm(x_2, envir = where))
setReactive("x_1", value = Sys.time(), where = where)
where$x_1
# [1] "2014-09-24 18:43:18 CEST"
setReactive("x_2", value = reactive(x_1 + 60*60*24, env = where), where = where)
where$x_2
# [1] "2014-09-25 18:43:18 CEST"
where$x_1 <- Sys.time()
where$x_1
# [1] "2014-09-25 18:43:52 CEST"
where$x_2
# [1] "2014-09-25 18:43:52 CEST"
如果不像我那样处理通过字符串操作中的env
切片,我将如何能够访问/更改定义反应性的实际函数/闭包的环境(以防止需要两次声明环境)?
func <- attributes(reactive(x_1 + 60*60*24))$observable$.func
func
# function ()
# x_1 + 60 * 60 * 24
# attr(,"_rs_shinyDebugPtr")
# <pointer: 0x0000000008930380>
# attr(,"_rs_shinyDebugId")
# [1] 858
# attr(,"_rs_shinyDebugLabel")
# [1] "Reactive"
修改强>
想出来:environment(func)
有没有办法实现&#34;相互依赖&#34;我用上面的代码实现了现有的闪亮功能吗?
只是一个&#34;遥远的&#34;没有特定用例的想法:是否有可能让观察到的变量同时存在于不同的环境中,并且reactive()
仍能正确识别它们?
再次感谢,乔!