Moduły Shiny wielokrotnego użytku
Hermetyzuj logikę interfejsu użytkownika i serwera w modułach z przestrzenią nazw, które można ponownie wykorzystywać.
Moduły Shiny wielokrotnego użytku to bezpłatna lekcja R Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej R Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs R Academy zawiera 4 lekcji w sumie.
Dlaczego warto używać modułów Shiny?
W miarę rozrastania się aplikacji Shiny umieszczanie całego kodu interfejsu użytkownika i serwera w jednym pliku staje się trudne do zarządzania. Moduły to samodzielne elementy interfejsu użytkownika Shiny i logiki serwera z identyfikatorami z przestrzenią nazw. Można wielokrotnie używać tego samego modułu w jednej aplikacji bez konfliktów identyfikatorów, a także niezależnie testować moduły.
# 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() — funkcja przestrzeni nazw
Każda funkcja interfejsu użytkownika modułu rozpoczyna się od ns <- NS(id). Wszystkie identyfikatory elementów interfejsu użytkownika przekazywane do Shiny muszą być opakowane w ns(). Funkcja ta dodaje prefiks id- do każdego identyfikatora, tworząc przestrzeń nazw, która zapobiega konfliktom między instancjami modułu.
# 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() — funkcja serwera
moduleServer(id, function(input, output, session) {...}) to nowoczesny (dostępny od Shiny 1.5) sposób definiowania logiki serwera modułu. Wewnątrz funkcji elementy input, output i session automatycznie korzystają z przestrzeni nazw — uzyskuje się dostęp do input$dataset, a nie do 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])
})
})
}Używanie modułów w aplikacji
Wywołaj funkcję interfejsu użytkownika modułu w ui, a funkcję serwera modułu w server, używając w obu przypadkach tego samego ciągu id. Możesz wywołać ten sam moduł wielokrotnie z różnymi identyfikatorami, aby utworzyć niezależne instancje.
# 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)Przekazywanie parametrów do interfejsu modułu
Funkcje interfejsu użytkownika modułu są zwykłymi funkcjami R. Dodaj parametry oprócz id, aby dostosować wygląd lub działanie każdej instancji w momencie jej tworzenia. Parametry te są obliczane jednokrotnie podczas budowania interfejsu użytkownika.
# 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')Przekazywanie wartości reaktywnych DO modułu
Funkcje serwera modułu mogą przyjmować wartości reaktywne lub wyrażenia reaktywne jako parametry. Dzięki temu aplikacja nadrzędna może przekazywać dane do modułów podrzędnych. W module należy wywołać funkcję reaktywną jak funkcję, aby uzyskać jej bieżącą wartość.
# 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)
}Zwracanie wartości reaktywnych Z modułu
Funkcje serwera modułu mogą zwracać wartości reaktywne do elementu nadrzędnego. Umożliwia to komunikację modułów podrzędnych w górę hierarchii. Zwróć wartość reaktywną lub listę wartości reaktywnych z moduleServer() i przypisz ją w funkcji serwera nadrzędnego.
# 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()) })
}Organizacja plików modułów
W dużych aplikacjach umieść każdy moduł w osobnym pliku w folderze R/. Shiny automatycznie wczytuje wszystkie pliki z folderu R/ podczas ładowania aplikacji. Dzięki temu każdy moduł pozostaje samodzielny i można go niezależnie testować za pomocą pakietu 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)Moduły zagnieżdżone
Moduły mogą zawierać inne moduły. Moduł nadrzędny przekazuje własną przestrzeń nazw session do wywołań modułów podrzędnych za pomocą argumentu session. Każdy poziom zagnieżdżenia dodaje kolejny prefiks przestrzeni nazw: 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-elementTestowanie modułów za pomocą shinytest2
Pakiet shinytest2 umożliwia pisanie automatycznych testów modułów przez opakowanie ich w minimalną aplikację. Użyj AppDriver, aby sterować przeglądarką, ustawiać dane wejściowe i sprawdzać wartości wyjściowe — bez korzystania z rzeczywistej sesji przeglądarki.
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')))
})Wzorce komunikacji między modułami
Podsumowanie wzorców komunikacji między modułami a aplikacją nadrzędną:
- Element nadrzędny do modułu: przekaż wartość reaktywną jako parametr do serwera modułu.
- Moduł do elementu nadrzędnego: zwróć wartość reaktywną z moduleServer().
- Między modułami równorzędnymi: element nadrzędny przechowuje współdzielony stan (
reactiveValues) i przekazuje go do każdego modułu. - Stan globalny: użyj
reactiveValueszdefiniowanego w elemencie nadrzędnym i przekaż do modułów odwołania do niego.
# 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
}Szybkie sprawdzenie
Dlaczego wszystkie identyfikatory elementów interfejsu użytkownika w funkcji interfejsu modułu muszą być opakowane w ns()?
Podsumowanie modułów Shiny
Najważniejsze informacje z lekcji „Moduły Shiny na potrzeby ponownego użycia kodu”:
- Moduły zapobiegają konfliktom identyfikatorów, dodając do wszystkich identyfikatorów przestrzeń nazw za pomocą
NS(id). - Interfejs modułu: zwykła funkcja z
ns <- NS(id); wszystkie identyfikatory należy opakować wns(). - Serwer modułu:
moduleServer(id, function(input, output, session) {...}). - Przekazuj wartości reaktywne DO modułów jako parametry funkcji; ZWRACAJ wartości reaktywne, aby umożliwić komunikację w górę hierarchii.
- Wywołuj ten sam moduł wielokrotnie z różnymi identyfikatorami, aby tworzyć niezależne instancje.
- Organizuj moduły w plikach
R/mod_*.R; Shiny automatycznie wczytuje folderR/. - Testuj moduły za pomocą
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)Często zadawane pytania
Czy lekcja „Moduły Shiny wielokrotnego użytku” jest bezpłatna?
Tak — pełny tekst „Moduły Shiny wielokrotnego użytku” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu R Academy, przejdź na CoddyKit PRO. Kurs R Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Moduły Shiny wielokrotnego użytku”?
Hermetyzuj logikę interfejsu użytkownika i serwera w modułach z przestrzenią nazw, które można ponownie wykorzystywać. Ćwiczysz R Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć R Academy?
Nie wymagamy żadnego doświadczenia. R Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „Moduły Shiny wielokrotnego użytku”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji R Academy?
Tak. Każda lekcja R Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Zaawansowane programowanie reaktywne
- Moduły Shiny wielokrotnego użytku
- Dynamiczny interfejs za pomocą renderUI i insertUI
- Wdrażanie aplikacji Shiny