如何将Shiny中重复的matrixInput函数封装为通用主函数?
问题说明
现有可正常运行的Shiny MWE代码实现较为粗糙:shinyMatrix包的matrixInput函数被重复调用3次,原作者尝试将其封装为自定义函数firstInput与secondInput,希望了解合并重复函数的最佳实践,以及是否出于可读性、调试便捷性考虑,移除自定义函数、全程直接使用完整的matrixInput写法更优。
原MWE代码
library(shiny) library(shinyMatrix) firstInput <- function(inputId,y){ # << y = y col default value of input matrix matrixInput(inputId, value = matrix(c(10,5), 1, 2, dimnames = list(c("1st input"),c("X and Y",""))), rows = list(extend = FALSE, names = TRUE), cols = list( extend = FALSE, names = TRUE, editableNames = FALSE, multiheader=TRUE ), class = "numeric")} secondInput <- function(inputId,y){ # << y = y col default value of input matrix matrixInput(inputId, value = matrix(c(10,y), 1, 2, dimnames = list(c("2nd input"),c(1,""))), label = "Add, delete, or modify matrix parameters:", rows = list(extend = FALSE, names = TRUE), cols = list( extend = TRUE, delta = 2, delete = TRUE, names = TRUE, editableNames = FALSE, multiheader=TRUE ), class = "numeric")} ui <- fluidPage( sidebarLayout( sidebarPanel( uiOutput("panel"), actionButton("showSecond","Show 2nd input (modal)",width = "100%") ), mainPanel(plotOutput("plot1")) ) ) server <- function(input, output, session){ output$panel <- renderUI({firstInput("input1")}) observeEvent(input$showSecond,{ showModal( modalDialog( if(is.null(input$input2)) {secondInput("input2",input$input1[1,2])} else {matrixInput("input2", value = input$input2, label = "Add, delete, or modify matrix parameters:", rows = list(extend = FALSE, names = TRUE), cols = list( extend = TRUE, delta = 2, delete = TRUE, names = TRUE, editableNames = FALSE, multiheader=TRUE ), class = "numeric") }, footer = modalButton("Close") )) }) observe({ # << Assign sequential col header to matrix based on groupings of two req(input$input2) mm <- input$input2 colnames(mm) <- trunc(1:ncol(mm)/2)+1 isolate(updateMatrixInput(session, "input2", mm)) }) output$secondInput <- renderUI({ req(input$input1) secondInput("input2",input$input1[1,2]) }) outputOptions(output,"secondInput",suspendWhenHidden = FALSE) output$plot1 <-renderPlot({ req(input$input1) plot(rep(if(isTruthy(input$input2)){input$input2[1,2]} else {input$input1[1,2]}, times=10)) }) } shinyApp(ui, server)
实践建议
- 优先保留封装逻辑,不要直接使用重复的
matrixInput写法。重复代码不仅冗余,后续需要修改通用配置(比如class="numeric"、multiheader=TRUE这类所有输入都一致的参数)时,修改一次封装函数即可全局生效,不需要逐处修改,反而能降低出错概率。 - 可以将现有的两个独立封装函数再合并为一个通用的
customMatrixInput,把差异点做成可传入的参数即可。你当前两个自定义函数的差异仅在矩阵行名、列名、列扩展配置、label显隐、默认值这几点,完全可以通过参数控制,比写两个分开的函数更简洁。 - 你代码中modal判断里第三次重复写的
matrixInput,也可以通过给封装函数加可选的value参数实现复用:如果传入了已存在的矩阵值就直接用,没传入就生成默认矩阵,不需要再重写一遍完整配置。 - 关于可读性和调试的顾虑:只要给封装函数加清晰的参数注释,明确每个参数的作用,可读性远高于散在各处的大段重复配置。调试时如果是矩阵输入的逻辑出问题,只需要排查封装函数这一处,比核对3处重复代码的差异效率高得多。
优化后的封装示例
# 通用自定义矩阵输入封装 # 参数说明: # inputId: 输入组件的id # y: 矩阵第二列默认值,仅未传入value时生效 # row_label: 矩阵行名 # col_names: 矩阵列名数组 # cols_extend: 是否允许列扩展 # show_label: 是否显示输入组件的顶部提示label # value: 可选,传入已存在的矩阵值时直接使用该值,不生成默认矩阵 customMatrixInput <- function(inputId, y = 5, row_label = "1st input", col_names = c("X and Y",""), cols_extend = FALSE, show_label = FALSE, value = NULL){ # 处理矩阵值 if(is.null(value)){ mat_value <- matrix(c(10,y), 1, 2, dimnames = list(c(row_label), col_names)) }else{ mat_value <- value } # 处理label input_label <- if(show_label) "Add, delete, or modify matrix parameters:" else NULL # 处理列配置 cols_config <- list( extend = cols_extend, names = TRUE, editableNames = FALSE, multiheader=TRUE ) if(cols_extend){ cols_config$delta <- 2 cols_config$delete <- TRUE } # 调用原生方法 matrixInput( inputId = inputId, value = mat_value, label = input_label, rows = list(extend = FALSE, names = TRUE), cols = cols_config, class = "numeric" ) }
优化后原来3处matrixInput的调用可以替换为:
- 第一个输入:
customMatrixInput("input1") - 第二个输入新建场景:
customMatrixInput("input2", y = input$input1[1,2], row_label = "2nd input", col_names = c(1,""), cols_extend = TRUE, show_label = TRUE) - 第二个输入已存在场景:
customMatrixInput("input2", value = input$input2, cols_extend = TRUE, show_label = TRUE)
内容的提问来源于stack exchange,提问作者Curious Jorge - user9788072
相关产品推荐
相关产品推荐

