重建Shiny中的reactiveValues,但不使用反应性(但具有相同的作用域行为)。

huangapple go评论107阅读模式
英文:

Recreate reactiveValues in shiny without reactiveness (but same scoping behaviour)

问题

以下是您要翻译的内容:

我有一个拆分成多个模块的Shiny应用程序。为了在这些模块之间传递数据,我使用reactiveVal(ues)。可以将这些传递给函数,并从其他函数中进行修改。

我想创建一个类似的对象,但不带内置的reactiveValues()的所有反应功能。我想传递相当大的数据框/表格,并希望手动控制何时以及何时进行数据表/计算。

在此示例中,我想创建一个对象,而不是list(),它可以像reactiveValues()一样在不同范围之间传递数据:

  1. library(shiny)
  2. ## 模块
  3. module_ui <- function(id) {
  4. ns <- NS(id)
  5. actionButton(inputId = ns("increase"), label = "Increase")
  6. }
  7. module_server <- function(id, vals, not_working) {
  8. moduleServer(id, function(input, output, session) {
  9. observeEvent(input$increase, {
  10. vals$a <- vals$a + 1
  11. not_working$a <- not_working$a + 1
  12. })
  13. })
  14. }
  15. ## 主要
  16. ui <- fluidPage(
  17. h1("ReactiveValues Test"),
  18. textOutput(outputId = "out1"),
  19. textOutput(outputId = "out2"),
  20. actionButton(inputId = "update", label = "Update Shown Values"),
  21. module_ui("modid")
  22. )
  23. server <- function(input, output, session) {
  24. vals <- reactiveValues(a = 10)
  25. not_working <- list(a = 10)
  26. observeEvent(input$update, {
  27. output$out1 <- renderText(paste("The value of vals$a is:", vals$a))
  28. output$out2 <- renderText(paste("The value of not_working$a is:", not_working$a))
  29. })
  30. module_server("modid", vals, not_working)
  31. }
  32. shinyApp(ui, server)

在上面的示例中:

  • 使用reactiveValues的问题在于,按下初始的“Update Shown Values”按钮后,当按下“increase”按钮时,vals$a的值会立即更新,而无需手动“Update Shown Values”。由于我有大量数据并进行一些计算,我不希望它自动触发,也不希望被迫仅在反应环境内读取和修改值。
  • 使用list的问题在于,该对象被复制到module_server调用中,因此在主函数范围内不会更新值。

我尝试阅读了reactives.R的源代码,但对我来说R语言的高级内容难以理解正在发生的事情。

我尝试寻找通过引用传递的方法,但尚未找到。但我认为通过引用传递list()可能会起作用。在Python中,传递字典会以我想要的方式进行操作,其中可以在函数范围内修改键/值对。

我该如何创建一个对象,其作用范围与reactiveValues相同,但没有所有的反应功能?
是否有任何描述reactives.R源代码中发生的事情的良好资源?(或者不是特定的代码,而是使用的语言功能)

英文:

I have a shiny app split into multiple modules. To pass data between the modules I use reactiveVal(ues). These can be passed to functions and modified from within the other functions.

I would like to create a similar object, but without all the reactive capabilities of the built-in reactiveValues(). I want to pass quite large dataframes/tibbles around and want to manually control if and when datatables/calculations are done.

In this example, I would like to create a object instead of list(), that can pass data around the scopes in the same way as reactiveValues():

  1. library(shiny)
  2. ## Module
  3. module_ui &lt;- function(id) {
  4. ns &lt;- NS(id)
  5. actionButton(inputId = ns(&quot;increase&quot;), label = &quot;Increase&quot;)
  6. }
  7. module_server &lt;- function(id, vals, not_working) {
  8. moduleServer(id, function(input, output, session) {
  9. observeEvent(input$increase, {
  10. vals$a &lt;- vals$a + 1
  11. not_working$a &lt;- not_working$a + 1
  12. })
  13. })
  14. }
  15. ## Main
  16. ui &lt;- fluidPage(
  17. h1(&quot;ReactiveValues Test&quot;),
  18. textOutput(outputId = &quot;out1&quot;),
  19. textOutput(outputId = &quot;out2&quot;),
  20. actionButton(inputId = &quot;update&quot;, label = &quot;Update Shown Values&quot;),
  21. module_ui(&quot;modid&quot;)
  22. )
  23. server &lt;- function(input, output, session) {
  24. vals &lt;- reactiveValues(a = 10)
  25. not_working &lt;- list(a = 10)
  26. observeEvent(input$update, {
  27. output$out1 &lt;- renderText(paste(&quot;The value of vals$a is:&quot;, vals$a))
  28. output$out2 &lt;- renderText(paste(&quot;The value of not_working$a is:&quot;, not_working$a))
  29. })
  30. module_server(&quot;modid&quot;, vals, not_working)
  31. }
  32. shinyApp(ui, server)

In the example above

  • the problem with reactiveValues is that after the inital "Update Shown Values" is pressed, the vals$a value is updated instantly when "increase"-button is pressed and does not require the manual "Update Shown Values". Because I have large amounts of data and do some calculation I don't want it to trigger automatically, and I don't want to be forced to only read and modify the value inside reactive environments.
  • the problem with list is that the object gets copied into the module_server-call and this the value is not updated in the main function scope.

I have tried to read the source code of reactives.R but it is too advanced R for me to understand what is happening.

I have tried to find a way to pass by reference, but not found one yet. But I think passing a list() by reference might work. In python passing a dictionary would behave in the way I want. Where key/value-pairs can be modified inside function scopes.

How could I go about created an object which scoping behaves as reactiveValues, but without all of the reactiveness-functionality?
Are there any good resources describing what is happening in the reactives.R source code? (or not that code specifically but the language-feature used)

答案1

得分: 0

以下是翻译好的部分:

"After some further digging and experimentation, I found that the underlying object that was used was a fastmap. If we wrap it in some S3 object notation we can use it as a replacement for reactiveValues() without the reactiveness.

This code is mostly copied from reactives.R. All credit goes there.

nolint start: object_name_linter.

dataHolder <- function(...) {
args <- rlang::list2(...)
if ((length(args) > 0) && (is.null(names(args)) || any(names(args) == ""))
rlang::abort("All arguments passed to dataHolder() must be named.")

values <- structure(
list(
impl = fastmap::fastmap()
),
class = "dataholder"
)

lapply(names(args),
(name) {
.subset2(values, "impl")$set(name, args[[name]])
})

values
}

checkName <- function(x) {
if (!is.character(x) || length(x) != 1) {
rlang::abort("Must use single string to index into dataholder.")
}
}

print.dataholder <- function(x, ...) {
cat("", "\n")
cat(" Values: ", paste0(.subset2(x, "impl")$keys(sort = TRUE), collapse = ", "), "\n")
}

is.dataholder <- function(x) inherits(x, "dataholder")

$.dataholder <- function(x, name) {
checkName(name)
.subset2(x, "impl")$get(name)
}

[[.dataholder <- $.dataholder

$<-.dataholder <- function(x, name, value) {
checkName(name)
.subset2(x, "impl")$set(name, value)
x
}

[[<-.dataholder <- $<-.dataholder

[.dataholder <- function(values, name) {
rlang::abort("Can't index dataholder with [.")
}

[<-.dataholder <- function(values, name, value) {
rlang::abort("Can't index dataholder with [.")
}

nolint end"

英文:

After some further digging and experimentation, I found that the underlying object that was used was a fastmap. If we wrap it in some S3 object notation we can use it as a replacement for reactiveValues() without the reactiveness.

This code is mostly copied from reactives.R. All credit goes there.

  1. # nolint start: object_name_linter.
  2. dataHolder &lt;- function(...) {
  3. args &lt;- rlang::list2(...)
  4. if ((length(args) &gt; 0) &amp;&amp; (is.null(names(args)) || any(names(args) == &quot;&quot;)))
  5. rlang::abort(&quot;All arguments passed to dataHolder() must be named.&quot;)
  6. values &lt;- structure(
  7. list(
  8. impl = fastmap::fastmap()
  9. ),
  10. class = &quot;dataholder&quot;
  11. )
  12. lapply(names(args),
  13. \(name) {
  14. .subset2(values, &quot;impl&quot;)$set(name, args[[name]])
  15. })
  16. values
  17. }
  18. checkName &lt;- function(x) {
  19. if (!is.character(x) || length(x) != 1) {
  20. rlang::abort(&quot;Must use single string to index into dataholder.&quot;)
  21. }
  22. }
  23. print.dataholder &lt;- function(x, ...) {
  24. cat(&quot;&lt;DataHolder&gt;&quot;, &quot;\n&quot;)
  25. cat(&quot; Values: &quot;, paste0(.subset2(x, &quot;impl&quot;)$keys(sort = TRUE), collapse = &quot;, &quot;), &quot;\n&quot;)
  26. }
  27. is.dataholder &lt;- function(x) inherits(x, &quot;dataholder&quot;)
  28. `$.dataholder` &lt;- function(x, name) {
  29. checkName(name)
  30. .subset2(x, &quot;impl&quot;)$get(name)
  31. }
  32. `[[.dataholder` &lt;- `$.dataholder`
  33. `$&lt;-.dataholder` &lt;- function(x, name, value) {
  34. checkName(name)
  35. .subset2(x, &quot;impl&quot;)$set(name, value)
  36. x
  37. }
  38. `[[&lt;-.dataholder` &lt;- `$&lt;-.dataholder`
  39. `[.dataholder` &lt;- function(values, name) {
  40. rlang::abort(&quot;Can&#39;t index dataholder with `[`.&quot;)
  41. }
  42. `[&lt;-.dataholder` &lt;- function(values, name, value) {
  43. rlang::abort(&quot;Can&#39;t index dataholder with `[`.&quot;)
  44. }
  45. # nolint end

huangapple
  • 本文由 发表于 2023年7月4日 22:32:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76613665.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定