0Pricing
R Academy · レッスン

リアクティブプログラミングの深掘り

リアクティブ値、reactive、observer、リアクティブグラフについて理解します。

「リアクティブプログラミングの深掘り」はCoddyKit上の無料R Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはR Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 R Academyコースには全4レッスンが含まれています。

リアクティビティ:基本概念

Shinyのリアクティブプログラミングモデルは、計算間の依存関係を自動的に追跡します。入力が変化すると、その入力に依存する出力だけが再実行され、それ以外は実行されません。この依存関係グラフを理解することが、効率的でバグのないShinyアプリを書く鍵です。

library(shiny)

# Simplest reactive relationship:
# output depends on input — Shiny tracks this automatically
server <- function(input, output, session) {
  output$result <- renderText({
    paste('You typed:', input$text_in)  # depends on text_in
  })
}

reactive({}) — 計算の共有

reactive({...})は、結果がキャッシュされ共有されるリアクティブ式を作成します。複数の出力で同じコストの高い計算を使う場合は、reactive()でラップすると、利用元ごとに計算するのではなく、変更ごとに1回だけ計算できます。関数のようにdata_subset()と呼び出します。

server <- function(input, output, session) {
  # Computed once, shared by multiple outputs
  filtered_data <- reactive({
    subset(mtcars, cyl == input$cyl_select)
  })

  output$plot  <- renderPlot({ plot(filtered_data()$wt,
                                    filtered_data()$mpg) })
  output$table <- renderTable({ filtered_data() })
  # filtered_data() is only recomputed once when cyl_select changes
}

reactiveVal() — 単一のリアクティブ値

reactiveVal(initial_value)は、プログラムから読み書きできるリアクティブ変数を作成します。引数なしで呼び出すと値を読み取り、新しい値を渡して呼び出すと値を書き込みます。複数のオブザーバーや出力が依存する可変状態を保持する場合に便利です。

server <- function(input, output, session) {
  counter <- reactiveVal(0)   # initial value = 0

  observeEvent(input$increment, {
    counter(counter() + 1)    # read current, write new
  })

  observeEvent(input$reset, {
    counter(0)                 # reset to 0
  })

  output$count <- renderText(paste('Count:', counter()))
}

reactiveValues() — 複数の状態変数

reactiveValues(key = value, ...)は、各要素がリアクティブな名前付きリストのようなものです。いずれかの要素が変化すると、その要素を読み取っている出力だけが無効化されます。一緒に更新する関連する状態変数をまとめるために使用します。

server <- function(input, output, session) {
  state <- reactiveValues(
    data         = NULL,
    current_page = 1,
    total_rows   = 0
  )

  observeEvent(input$load_btn, {
    state$data       <- read.csv(input$file$datapath)
    state$total_rows <- nrow(state$data)
    state$current_page <- 1
  })

  output$info <- renderText({
    paste('Page', state$current_page, '| Rows:', state$total_rows)
  })
}

observeEvent() — イベントへの応答

observeEvent(eventExpr, handlerExpr)は、eventExprが変化するたびにhandlerExprを実行します。状態の更新、データベースへの書き込み、ファイルのダウンロード、タブへの移動などの副作用に使用します。ハンドラーは値を返しません。

server <- function(input, output, session) {
  observeEvent(input$save_btn, {
    # Runs once each time save_btn is clicked
    write.csv(current_data(), '/tmp/export.csv', row.names = FALSE)
    showNotification('Saved!', type = 'message')
  })

  observeEvent(input$reset_btn, {
    updateTextInput(session, 'search', value = '')
    updateSliderInput(session, 'range', value = c(0, 100))
  })
}

eventReactive() — 必要なときに値を計算

eventReactive(eventExpr, valueExpr)はreactive()に似ていますが、依存関係が変化するたびではなく、特定のイベント(ボタンのクリックなど)が発生したときだけ再計算します。明示的なユーザー操作があったときだけ、負荷の高い計算を実行する場合に使用します。

server <- function(input, output, session) {
  # Only re-run the model when 'Run Model' is clicked
  model_result <- eventReactive(input$run_btn, {
    # Expensive computation — only on button click
    lm(as.formula(input$formula), data = get(input$dataset))
  })

  output$summary <- renderPrint({
    summary(model_result())
  })
}

isolate() — 依存関係を作らずに読み取る

isolate({expr})は、リアクティブな値や式を読み取りますが、リアクティブな依存関係は作成しません。分離された値が変化しても、それを囲む計算は再実行されません。変更を監視せずに現在の状態へアクセスする場合は、observe()またはobserveEvent()内で使用します。

server <- function(input, output, session) {
  observeEvent(input$add_row_btn, {
    # Read current_data without depending on it
    current <- isolate(data_rv())
    new_row <- data.frame(
      id    = nrow(current) + 1,
      value = input$new_value
    )
    data_rv(rbind(current, new_row))
  })
}

invalidateLater() — 自動更新

リアクティブコンテキスト内のinvalidateLater(ms)は、msミリ秒後にそのコンテキストを再実行するようスケジュールします。ライブデータフィードの更新、時計の更新、一定間隔でのデータベースの新しい行の確認など、ポーリングに使用します。

server <- function(input, output, session) {
  live_data <- reactive({
    invalidateLater(5000)   # re-run every 5 seconds
    # Fetch fresh data from external source
    httr2::request('https://api.example.com/stats') |>
      httr2::req_perform() |>
      httr2::resp_body_json()
  })

  output$live_plot <- renderPlot({
    plot(live_data()$time, live_data()$value, type = 'l')
  })
}

reactlog — リアクティブグラフをデバッグする

reactlogパッケージは、リアクティブな依存関係グラフを可視化します。アプリを実行する前に有効にし、操作した後でreactlog_show()を呼び出すと、どのリアクティブノードがどの順序で無効化されたかを確認できます。複雑なアプリのデバッグに不可欠です。

library(reactlog)

# Enable reactlog BEFORE launching the app
reactlog_enable()

# Launch your app
shinyApp(ui, server)

# After interacting in the browser, in the R console:
shiny::reactlogShow()

# A viewer opens showing the reactive graph
# with timestamps and invalidation chains

リアクティブ分離のアンチパターン

よくある間違いは、非リアクティブなコンテキスト内で誤ってリアクティブな値を読み取ること、または分離すべき計算内でリアクティブな値を読み取って不要な依存関係を作成することです。依存関係を作成する場所と、一度だけ状態のスナップショットを読み取る場所を、常に意図的に決めてください。

server <- function(input, output, session) {
  # WRONG: output depends on input$name, but also re-runs
  # every time input$slider changes (unintended dependency)
  output$msg <- renderText({
    paste(input$name, 'total:', input$slider * 2)
  })

  # CORRECT: output only responds to input$name changes;
  # slider is read as a snapshot
  output$msg_correct <- renderText({
    slider_val <- isolate(input$slider)
    paste(input$name, 'snapshot total:', slider_val * 2)
  })
}

observe() と observeEvent()

observe({...})は、読み取ったリアクティブな値のいずれかが変化するたびに自動で再実行されるリアクティブオブザーバーを作成します。observeEvent(event, {...})は、より制御しやすく、特定のイベントが発生したときだけ実行されます。意図しない再実行を避けるため、ボタン操作による副作用にはobserveEvent()を優先してください。

server <- function(input, output, session) {
  # observe: re-runs on ANY change in input$x or input$y
  observe({
    cat('x or y changed:', input$x, input$y, '\n')
  })

  # observeEvent: only runs when button is clicked
  observeEvent(input$submit_btn, {
    cat('Form submitted with x =', isolate(input$x), '\n')
  }, ignoreNULL = TRUE, ignoreInit = TRUE)
}

確認問題

reactive({})とeventReactive(event, {})の主な違いは何ですか。

リアクティブプログラミングのまとめ

リアクティブプログラミング徹底解説の要点:

  • reactive({}): 計算結果をキャッシュし、依存関係が変化すると再実行します。
  • reactiveVal(init): 1つの変更可能なリアクティブ変数です。val()で読み取り、val(new)で書き込みます。
  • reactiveValues(...): 名前付きのリアクティブな状態変数のリストです。
  • observeEvent(event, {}): 特定のイベントによってトリガーされる副作用です。
  • eventReactive(event, {}): 特定のイベントが発生したときだけ計算される値です。
  • isolate({}): 依存関係を作成せずにリアクティブな値を読み取ります。
  • invalidateLater(ms): ライブ更新のために定期的な再実行をスケジュールします。
  • reactlogを使用して、リアクティブな依存関係グラフを可視化し、デバッグします。
server <- function(input, output, session) {
  # State
  rv <- reactiveValues(data = NULL, n = 0)

  # Load data on button click
  observeEvent(input$load, {
    rv$data <- read.csv(input$file$datapath)
    rv$n    <- nrow(rv$data)
  })

  # Expensive model only on 'Run' click
  model <- eventReactive(input$run, {
    lm(y ~ ., data = isolate(rv$data))
  })

  output$summary <- renderPrint({ summary(model()) })
}

よくある質問

「リアクティブプログラミングの深掘り」レッスンは無料ですか?

はい。「リアクティブプログラミングの深掘り」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、R Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 R Academyコースには全4レッスンが含まれています。

「リアクティブプログラミングの深掘り」で何を学びますか?

リアクティブ値、reactive、observer、リアクティブグラフについて理解します。 ブラウザで直接実行するハンズオンコードでR Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

R Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのR Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「リアクティブプログラミングの深掘り」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このR Academyレッスンでコードを書いて実行できますか?

はい。すべてのR Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. リアクティブプログラミングの深掘り
  2. コード再利用のための Shiny モジュール
  3. renderUI と insertUI による動的 UI
  4. Shiny アプリをデプロイする
← R Academyに戻る