对于我正在构建的Shiny应用程序的一部分,我需要让用户选择一个目录.目录路径存储在一个反应变量中.用户可以从文件窗口中选择目录,也可以手动输入路径textInput.我已经想出如何做到这一点,但我不明白为什么我的解决方案有效!工作应用程序的最小示例:
library(shiny)
ui <- fluidPage(
actionButton("button1", "First Button"),
textInput("inText", "Input Text"),
actionButton("button2", "Second Button"),
textOutput("outText"),
textOutput("outFiles")
)
server <- function(input, output) {
values <- reactiveValues(inDir = NULL)
observeEvent(input$button1, {values$inDir <- tcltk::tk_choose.dir()})
observeEvent(input$button2, {values$inDir <- input$inText})
inPath <- eventReactive(values$inDir, {values$inDir})
output$outText <- renderText(inPath())
fileList <- reactive(list.files(path=inPath()))
output$outFiles <- renderPrint(fileList())
}
shinyApp(ui, server)
Run Code Online (Sandbox Code Playgroud)
我尝试的第一件事就是使用eventReactive并将两个输入源分配给反应变量:
server <- function(input, output) {
inPath <- eventReactive(input$button1, {tcltk::tk_choose.dir()})
inPath <- eventReactive(input$button2, {input$inText})
output$outText <- renderText(inPath())
fileList <- reactive(list.files(path=inPath()))
output$outFiles <- renderPrint(fileList())
}
Run Code Online (Sandbox Code Playgroud)
据我所知,这种效果只有一个按钮可以做任何事情.我真正不明白的是为什么这不起作用.我认为会发生的第一个按钮会创建inPath,然后推送会更新值并触发对相关值的更新(此处output$outText).到底发生了什么?
我试过的第二件事就是基于这个答案:
server <- function(input, output) {
values <- reactiveValues(inDir = NULL)
observeEvent(input$button1, {values$inDir <- tcltk::tk_choose.dir()})
observeEvent(input$button2, {values$inDir <- input$inText})
inPath <- reactive({if(is.null(values$inDir)) return()
values$inDir})
output$outText <- renderText(inPath())
fileList <- reactive(list.files(path=inPath()))
output$outFiles <- renderPrint(fileList())
}
Run Code Online (Sandbox Code Playgroud)
这可以正常工作,除了它显示"错误:无效'路径'参数"消息list.files.我认为这可能意味着fileList正在评估inPath = NULL.为什么当我使用reactive而不是eventReactive?
谢谢!
您可以摆脱inPath反应性并直接使用values$inDir。您将req()等待直到值可用。否则你会得到同样的错误(无效的“路径”参数)。
reactive立即触发,而将eventReactive等待给定事件发生并调用 eventReactive。
并且if(is.null(values$inDir)) return()无法正常工作,因为如果values$inDir为 NULL,它将返回 NULL,然后将其传递给list.files. 并list.files(NULL)给出错误:invalid 'path' argument。
将其替换为req(values$inDir),您将不会收到该错误。
你的2 inPath - eventReactive 示例将不起作用,因为第一个将被第二个覆盖,因此input$button1不会触发任何内容。
library(shiny)
ui <- fluidPage(
actionButton("button1", "First Button"),
textInput("inText", "Input Text"),
actionButton("button2", "Second Button"),
textOutput("outText"),
textOutput("outFiles")
)
server <- function(input, output) {
values <- reactiveValues(inDir = NULL)
observeEvent(input$button1, {values$inDir <- tcltk::tk_choose.dir()})
observeEvent(input$button2, {values$inDir <- input$inText})
output$outText <- renderText(values$inDir)
fileList <- reactive({
req(values$inDir);
list.files(path=values$inDir)
})
output$outFiles <- renderPrint(fileList())
}
shinyApp(ui, server)
Run Code Online (Sandbox Code Playgroud)
您还可以使用eventReactiveforbutton1和observeEventfor button2,但请注意,您需要额外的东西observe({ inPath() })才能使其工作。我更喜欢上面的解决方案,因为它更清楚发生了什么并且代码也更少。
server <- function(input, output) {
values <- reactiveValues(inDir = NULL)
inPath = eventReactive(input$button1, {values$inDir <- tcltk::tk_choose.dir()})
observe({
inPath()
})
observeEvent(input$button2, {values$inDir <- input$inText})
output$outText <- renderText(values$inDir)
fileList <- reactive({
req(values$inDir);
list.files(path=values$inDir)
})
output$outFiles <- renderPrint(fileList())
}
Run Code Online (Sandbox Code Playgroud)
为了说明为什么if(is.null(values$inDir)) return()不起作用,请考虑以下函数:
test <- function() { if (TRUE) return() }
test()
Run Code Online (Sandbox Code Playgroud)
尽管if条件的计算结果为TRUE,但仍然会有一个返回值(在本例中为NULL),该返回值将传递给以下函数,在您的情况下list.files,这将导致错误。