وحدات Shiny لإعادة استخدام التعليمات البرمجية
غلّف منطق واجهة المستخدم والخادم في وحدات قابلة لإعادة الاستخدام ومزوّدة بنطاقات أسماء
وحدات Shiny لإعادة استخدام التعليمات البرمجية درس مجاني في R Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في R Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة R Academy 4 دروس في المجموع.
لماذا نستخدم Shiny Modules؟
مع ازدياد حجم تطبيقات Shiny، يصبح وضع كل شيفرة واجهة المستخدم والخادم في ملف واحد أمرًا يصعب إدارته. الوحدات البرمجية Modules هي أجزاء مستقلة تحتوي على واجهة مستخدم Shiny ومنطق الخادم، وتستخدم معرّفات ذات نطاق أسماء. يمكنك إعادة استخدام الوحدة نفسها عدة مرات في تطبيق واحد دون تعارض في المعرّفات، كما يمكنك اختبار الوحدات بصورة مستقلة.
# 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() — دالة نطاق الأسماء
تبدأ كل دالة لواجهة مستخدم وحدة برمجية بالسطر ns <- NS(id). يجب تغليف جميع معرّفات عناصر واجهة المستخدم المُمرّرة إلى Shiny داخل ns(). فهذا يضيف id- إلى بداية كل معرّف، وينشئ نطاق أسماء يمنع التعارض بين مثيلات الوحدة.
# 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() — دالة الخادم
تُعد moduleServer(id, function(input, output, session) {...}) الطريقة الحديثة، منذ Shiny 1.5، لتعريف منطق خادم الوحدة البرمجية. داخل الدالة، تُحدَّد تلقائيًا نطاقات أسماء input وoutput وsession؛ لذلك تصل إلى input$dataset وليس 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])
})
})
}استخدام الوحدات البرمجية في التطبيق
استدعِ دالة واجهة مستخدم الوحدة داخل ui، ودالة خادم الوحدة داخل server، مع استخدام سلسلة id نفسها في كلتيهما. ويمكنك استدعاء الوحدة نفسها عدة مرات باستخدام معرّفات مختلفة لإنشاء مثيلات مستقلة.
# 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)تمرير المعلمات إلى واجهة مستخدم الوحدة
دوال واجهة مستخدم الوحدات البرمجية هي مجرد دوال R عادية. أضف معلمات إضافية إلى جانب id لتخصيص مظهر كل مثيل أو سلوكه وقت إنشائه. وتُقيَّم هذه المعلمات مرة واحدة عند إنشاء واجهة المستخدم.
# 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')تمرير القيم التفاعلية إلى وحدة برمجية
يمكن لدوال خادم الوحدات البرمجية قبول قيم أو تعبيرات تفاعلية كمعلمات. ويتيح ذلك للتطبيق الأب تمرير البيانات إلى الوحدات الابنة. داخل الوحدة، استدعِ القيمة التفاعلية كدالة للحصول على قيمتها الحالية.
# 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)
}إرجاع القيم التفاعلية من وحدة برمجية
يمكن لدوال خادم الوحدات البرمجية إرجاع قيم تفاعلية إلى التطبيق الأب. ويتيح ذلك للوحدات الابنة التواصل مع المستوى الأعلى. أرجِع قيمة تفاعلية أو قائمة من القيم التفاعلية من moduleServer()، ثم خزّنها في دالة خادم التطبيق الأب.
# 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()) })
}تنظيم ملفات الوحدات البرمجية
في التطبيقات الكبيرة، ضع كل وحدة برمجية في ملفها الخاص داخل مجلد R/. يحمّل Shiny تلقائيًا مصدر جميع الملفات الموجودة في R/ عند تحميل التطبيق. ويحافظ ذلك على استقلالية كل وحدة، ويجعل اختبارها بصورة مستقلة باستخدام حزمة 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)الوحدات البرمجية المتداخلة
يمكن أن تحتوي الوحدات البرمجية على وحدات أخرى. ويمرّر الأب نطاق أسماء session الخاص به إلى استدعاءات الوحدات الابنة باستخدام المعلمة session. ويضيف كل مستوى من مستويات التداخل بادئة نطاق أسماء أخرى: 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-elementاختبار الوحدات البرمجية باستخدام shinytest2
تتيح لك حزمة shinytest2 كتابة اختبارات آلية للوحدات البرمجية من خلال تغليفها داخل تطبيق مصغّر. استخدم AppDriver للتحكم في المتصفح، وتعيين المدخلات، والتحقق من قيم المخرجات، وكل ذلك دون جلسة متصفح حقيقية.
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')))
})أنماط التواصل بين الوحدات البرمجية
ملخص لأنماط التواصل بين الوحدات البرمجية والتطبيق الأب:
- من الأب إلى الوحدة: مرّر قيمة تفاعلية كمعلمة إلى خادم الوحدة.
- من الوحدة إلى الأب: أرجِع قيمة تفاعلية من moduleServer().
- بين الوحدات الشقيقة: يحتفظ الأب بالحالة المشتركة (reactiveValues) ويمرّرها إلى كل وحدة.
- الحالة العامة: استخدم
reactiveValuesالمعرّفة في الأب، ومرّر مراجع إليها إلى الأسفل.
# 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
}تحقق سريع
لماذا يجب تغليف جميع معرّفات عناصر واجهة المستخدم في دالة واجهة مستخدم الوحدة باستخدام ns()؟
مراجعة وحدات Shiny
أهم النقاط المستخلصة من وحدات Shiny لإعادة استخدام الشيفرة:
- تمنع الوحدات تعارض المعرّفات من خلال تحديد نطاق أسماء جميع المعرّفات باستخدام
NS(id). - واجهة مستخدم الوحدة: دالة عادية تتضمن
ns <- NS(id)؛ غلّف جميع المعرّفات باستخدامns(). - خادم الوحدة:
moduleServer(id, function(input, output, session) {...}). - مرّر القيم التفاعلية إلى الوحدات كمعلمات للدالة، وأرجِع القيم التفاعلية للتواصل مع المستوى الأعلى.
- استدعِ الوحدة نفسها عدة مرات باستخدام معرّفات مختلفة لإنشاء مثيلات مستقلة.
- نظّم الوحدات في ملفات
R/mod_*.R؛ إذ يحمّل Shiny مجلدR/تلقائيًا. - اختبر الوحدات باستخدام
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)الأسئلة الشائعة
هل درس «وحدات Shiny لإعادة استخدام التعليمات البرمجية» مجاني؟
نعم — نص درس «وحدات Shiny لإعادة استخدام التعليمات البرمجية» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة R Academy، انتقل إلى CoddyKit PRO. تتضمن دورة R Academy 4 دروس في المجموع.
ماذا ستتعلم في «وحدات Shiny لإعادة استخدام التعليمات البرمجية»؟
غلّف منطق واجهة المستخدم والخادم في وحدات قابلة لإعادة الاستخدام ومزوّدة بنطاقات أسماء تتمرن على R Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ R Academy؟
لا تُشترط خبرة سابقة. R Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «وحدات Shiny لإعادة استخدام التعليمات البرمجية»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس R Academy هذا؟
نعم. كل درس في R Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- تعمّق في البرمجة التفاعلية
- وحدات Shiny لإعادة استخدام التعليمات البرمجية
- واجهة مستخدم ديناميكية باستخدام renderUI وinsertUI
- نشر تطبيقات Shiny