Moduli Shiny per riutilizzare il codice
Incapsuli la logica dell'interfaccia e del server in moduli riutilizzabili con namespace
Moduli Shiny per riutilizzare il codice è una lezione R Academy gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento R Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso R Academy include 4 lezioni in totale.
Perché utilizzare i moduli Shiny?
Quando le app Shiny crescono, diventa ingestibile avere tutto il codice dell'interfaccia e del server in un unico file. I moduli sono componenti autonomi costituiti dalla UI Shiny e dalla logica del server, con ID dotati di namespace. Può riutilizzare lo stesso modulo più volte nella stessa app senza conflitti tra ID e testare i moduli in modo indipendente.
# Problem: without modules, ID conflicts arise
# ui <- fluidPage(
# selectInput('dataset', ...), # used by plot1 AND plot2!
# selectInput('dataset', ...) # duplicate ID — BROKEN
# )
# With modules: each instance has its own namespaced IDs
# plotModule('plot1', ...) -> input$plot1-dataset
# plotModule('plot2', ...) -> input$plot2-datasetNS() — La funzione namespace
Ogni funzione UI di un modulo inizia con ns <- NS(id). Tutti gli ID degli elementi UI passati a Shiny devono essere racchiusi in ns(). In questo modo viene anteposto id- a ciascun ID, creando un namespace che previene i conflitti tra istanze del modulo.
# Module UI function
filter_plot_ui <- function(id) {
ns <- NS(id) # create the namespace function
tagList(
selectInput(ns('dataset'), 'Choose Dataset:',
choices = c('mtcars', 'iris', 'airquality')),
sliderInput(ns('n_rows'), 'Rows to show:', 1, 50, 20),
plotOutput(ns('scatter_plot'))
)
}moduleServer() — La funzione del server
moduleServer(id, function(input, output, session) {...}) è il modo moderno, a partire da Shiny 1.5, per definire la logica del server di un modulo. All'interno della funzione, input, output e session vengono automaticamente dotati di namespace: si accede a input$dataset, non a input$plot1-dataset.
# Module server function
filter_plot_server <- function(id) {
moduleServer(id, function(input, output, session) {
data <- reactive({
# input$dataset is already namespaced to this instance
head(get(input$dataset), input$n_rows)
})
output$scatter_plot <- renderPlot({
df <- data()
plot(df[[1]], df[[2]],
xlab = names(df)[1], ylab = names(df)[2])
})
})
}Utilizzo dei moduli nell'app
Chiami la funzione UI del modulo in ui e la funzione server del modulo in server, utilizzando in entrambi i casi la stessa stringa id. Può chiamare più volte lo stesso modulo con ID diversi per creare istanze indipendenti.
# Main app using the module twice
ui <- fluidPage(
h2('Plot 1'),
filter_plot_ui('plot1'), # instance 1
hr(),
h2('Plot 2'),
filter_plot_ui('plot2') # instance 2 — no ID conflicts!
)
server <- function(input, output, session) {
filter_plot_server('plot1') # wire up instance 1
filter_plot_server('plot2') # wire up instance 2
}
shinyApp(ui, server)Passaggio di parametri alla UI del modulo
Le funzioni UI dei moduli sono normali funzioni R. Aggiunga parametri oltre a id per personalizzare l'aspetto o il comportamento di ogni istanza al momento della creazione. Questi parametri vengono valutati una volta sola quando viene costruita la UI.
# Module UI with extra parameters
summary_table_ui <- function(id, title = 'Summary', height = '300px') {
ns <- NS(id)
tagList(
h4(title),
div(
style = paste0('height:', height, '; overflow-y: auto;'),
DTOutput(ns('tbl'))
)
)
}
# Use with custom titles
summary_table_ui('train_tbl', title = 'Training Data', height = '400px')
summary_table_ui('test_tbl', title = 'Test Data', height = '200px')Passaggio di reattivi AL modulo
Le funzioni server dei moduli possono accettare valori o espressioni reattive come parametri. Questo consente all'app principale di passare dati ai moduli figlio. All'interno del modulo, chiami il reattivo come una funzione per ottenere il suo valore corrente.
# Module that accepts a reactive as input
chart_module_server <- function(id, data_reactive) {
moduleServer(id, function(input, output, session) {
output$chart <- renderPlot({
df <- data_reactive() # call the reactive passed in
ggplot2::ggplot(df, ggplot2::aes(x = x, y = y)) +
ggplot2::geom_point(colour = input$colour)
})
})
}
# In main server:
server <- function(input, output, session) {
shared_data <- reactive({ load_data(input$source) })
chart_module_server('chart1', data_reactive = shared_data)
chart_module_server('chart2', data_reactive = shared_data)
}Restituzione di reattivi DA un modulo
Le funzioni server dei moduli possono restituire valori reattivi all'app principale. Questo consente ai moduli figlio di comunicare verso l'alto. Restituisca un reattivo o un elenco di reattivi da moduleServer() e lo assegni nella funzione server dell'app principale.
# Module that returns a reactive to the parent
filter_module_server <- function(id, all_data) {
moduleServer(id, function(input, output, session) {
# Return the filtered data reactive
filtered <- reactive({
all_data[all_data$group == input$group_filter, ]
})
return(filtered) # parent can use this reactive
})
}
# In main server:
server <- function(input, output, session) {
raw_data <- reactive({ read.csv('data.csv') })
# filtered_data is a reactive returned from the module
filtered_data <- filter_module_server('filter1', raw_data)
output$main_plot <- renderPlot({ plot(filtered_data()) })
}Organizzazione dei file dei moduli
Per le app di grandi dimensioni, inserisca ogni modulo in un file dedicato nella cartella R/. Shiny carica automaticamente tutti i file presenti in R/ quando l'app viene avviata. In questo modo ogni modulo rimane autonomo e può essere testato in modo indipendente utilizzando il pacchetto shinytest2.
# Recommended project structure:
# myapp/
# app.R # main app: source modules + wire up
# R/
# mod_filter_plot.R # filter_plot_ui() + filter_plot_server()
# mod_summary_table.R # summary_table_ui() + summary_table_server()
# mod_download.R # download_ui() + download_server()
# tests/
# testthat/test-mod_filter_plot.R
# In app.R:
library(shiny)
# source('R/mod_filter_plot.R') # not needed if in R/ folder
ui <- fluidPage(filter_plot_ui('p1'))
server <- function(input, output, session) { filter_plot_server('p1') }
shinyApp(ui, server)Moduli annidati
I moduli possono contenere altri moduli. Il modulo padre passa il proprio namespace session alle chiamate dei moduli figlio utilizzando l'argomento session. Ogni livello di annidamento aggiunge un altro prefisso al namespace: outer-inner-element_id.
# Outer module uses an inner module
outer_server <- function(id) {
moduleServer(id, function(input, output, session) {
# Call an inner module using this module's session
inner_result <- inner_module_server(
id = 'inner',
session = session # passes the namespaced session
)
output$combined <- renderText({
paste('Inner result:', inner_result())
})
})
}
# ID chain: outer -> inner
# Full ID: outer-inner-elementTest dei moduli con shinytest2
Il pacchetto shinytest2 consente di scrivere test automatizzati per i moduli racchiudendoli in un'app minima. Utilizzi AppDriver per interagire con il browser, impostare gli input e verificare i valori degli output, il tutto senza una vera sessione del browser.
library(shinytest2)
# Wrap the module in a testable app
test_that('filter_plot module filters correctly', {
test_app <- shinyApp(
ui = fluidPage(filter_plot_ui('test')),
server = function(input, output, session) {
filter_plot_server('test')
}
)
app <- AppDriver$new(test_app)
app$set_inputs('test-dataset' = 'iris') # namespaced input
app$wait_for_idle()
# Assert plot was rendered
expect_true(!is.null(app$get_value(output = 'test-scatter_plot')))
})Modelli di comunicazione tra moduli
Riepilogo dei modelli di comunicazione tra i moduli e l'app principale:
- Dal padre al modulo: passi un reattivo come parametro alla funzione server del modulo.
- Dal modulo al padre: restituisci un reattivo da moduleServer().
- Tra moduli fratelli: il padre conserva lo stato condiviso (reactiveValues) e lo passa a ciascun modulo.
- Stato globale: utilizzi
reactiveValuesdefinito nel padre e ne passi i riferimenti.
# Sibling module communication via parent state
server <- function(input, output, session) {
shared <- reactiveValues(selected_row = NULL)
# Table module sets the selection
table_module_server('tbl', shared_state = shared)
# Detail module reads the selection
detail_module_server('detail', shared_state = shared)
# Both modules communicate through 'shared' reactiveValues
# Table sets shared$selected_row; Detail reads it
}Verifica rapida
Perché tutti gli ID degli elementi UI in una funzione UI di modulo devono essere racchiusi in ns()?
Riepilogo dei moduli Shiny
Punti chiave dei moduli Shiny per il riutilizzo del codice:
- I moduli prevengono i conflitti tra ID aggiungendo un namespace a tutti gli ID con
NS(id). - UI del modulo: funzione normale con
ns <- NS(id); racchiuda tutti gli ID inns(). - Server del modulo:
moduleServer(id, function(input, output, session) {...}). - Passi i reattivi AI moduli come parametri di funzione; RESTITUISCA i reattivi per la comunicazione verso l'alto.
- Chiami più volte lo stesso modulo con ID diversi per ottenere istanze indipendenti.
- Organizzi i moduli nei file
R/mod_*.R; Shiny carica automaticamente la cartellaR/. - Testi i moduli con
shinytest2::AppDriver.
# Complete module example
my_module_ui <- function(id) {
ns <- NS(id)
tagList(selectInput(ns('var'), 'Variable:', choices = names(mtcars)),
plotOutput(ns('hist')))
}
my_module_server <- function(id, data) {
moduleServer(id, function(input, output, session) {
output$hist <- renderPlot(hist(data()[[input$var]]))
})
}
# Use it:
ui <- fluidPage(my_module_ui('m1'), my_module_ui('m2'))
server <- function(input, output, session) {
d <- reactive(mtcars)
my_module_server('m1', d)
my_module_server('m2', d)
}
shinyApp(ui, server)Domande Frequenti
La lezione «Moduli Shiny per riutilizzare il codice» è gratuita?
Sì — il testo completo di «Moduli Shiny per riutilizzare il codice» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso R Academy, passa a CoddyKit PRO. Il corso R Academy include 4 lezioni in totale.
Cosa imparerò in «Moduli Shiny per riutilizzare il codice»?
Incapsuli la logica dell'interfaccia e del server in moduli riutilizzabili con namespace Eserciti R Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare R Academy?
Non è richiesta alcuna esperienza precedente. R Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.
Quanto tempo richiede la lezione «Moduli Shiny per riutilizzare il codice»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione R Academy?
Sì. Ogni lezione R Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Approfondimento della programmazione reattiva
- Moduli Shiny per riutilizzare il codice
- Interfaccia dinamica con renderUI e insertUI
- Distribuzione delle app Shiny