0Pricing
R Academy · บทเรียน

โมดูล Shiny เพื่อการนำโค้ดกลับมาใช้ใหม่

ห่อหุ้มตรรกะส่วนติดต่อผู้ใช้และเซิร์ฟเวอร์ไว้ในโมดูลที่มีเนมสเปซและนำกลับมาใช้ใหม่ได้

โมดูล Shiny เพื่อการนำโค้ดกลับมาใช้ใหม่ เป็นบทเรียน R Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน R Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส R Academy มีบทเรียนทั้งหมด 4 บทเรียน

เหตุใดจึงควรใช้โมดูล Shiny

เมื่อแอป Shiny มีขนาดใหญ่ขึ้น การเก็บโค้ดส่วนติดต่อผู้ใช้และโค้ดเซิร์ฟเวอร์ทั้งหมดไว้ในไฟล์เดียวจะจัดการได้ยาก โมดูล คือส่วนของตรรกะส่วนติดต่อผู้ใช้และเซิร์ฟเวอร์ของ 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-dataset

NS() — ฟังก์ชันเนมสเปซ

ฟังก์ชันส่วนติดต่อผู้ใช้ของทุกโมดูลจะเริ่มต้นด้วย 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')

การส่งค่ารีแอกทีฟ INTO โมดูล

ฟังก์ชันเซิร์ฟเวอร์ของโมดูลสามารถรับค่ารีแอกทีฟหรือนิพจน์รีแอกทีฟเป็นพารามิเตอร์ได้ ทำให้แอปแม่สามารถส่งข้อมูลลงไปยังโมดูลลูกได้ ภายในโมดูล ให้เรียกค่ารีแอกทีฟเหมือนฟังก์ชันเพื่อรับค่าปัจจุบัน

# 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)
}

การส่งคืนค่ารีแอกทีฟ FROM โมดูล

ฟังก์ชันเซิร์ฟเวอร์ของโมดูลสามารถส่งคืนค่ารีแอกทีฟให้แอปแม่ได้ ทำให้โมดูลลูกสื่อสารกลับขึ้นไปได้ ให้ส่งคืนค่ารีแอกทีฟหรือรายการค่ารีแอกทีฟจาก 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 เพื่อการนำโค้ดกลับมาใช้ใหม่” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส R Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส R Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “โมดูล Shiny เพื่อการนำโค้ดกลับมาใช้ใหม่”

ห่อหุ้มตรรกะส่วนติดต่อผู้ใช้และเซิร์ฟเวอร์ไว้ในโมดูลที่มีเนมสเปซและนำกลับมาใช้ใหม่ได้ คุณปฏิบัติ R Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน R Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน R Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “โมดูล Shiny เพื่อการนำโค้ดกลับมาใช้ใหม่” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน R Academy นี้ได้ไหม

ได้ บทเรียน R Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. เจาะลึกการเขียนโปรแกรมแบบรีแอกทีฟ
  2. โมดูล Shiny เพื่อการนำโค้ดกลับมาใช้ใหม่
  3. ส่วนติดต่อผู้ใช้แบบไดนามิกด้วย renderUI และ insertUI
  4. การนำแอป Shiny ไปใช้งาน
← กลับไปที่ R Academy