You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React前端对接R Shiny后端:交互式仪表盘双向数据传递方法咨询

React 前端与 R Shiny 后端双向数据传递实现方案

以下是两种核心的双向数据传递实现方式,均基于Shiny原生通信机制,无需额外第三方中间件:

一、基于Shiny自定义消息的灵活通信

这种方式适合快速实现轻量级双向交互,直接利用Shiny的websocket通道传递自定义消息。

完整示例代码

R Shiny 后端代码

library(shiny)

ui <- fluidPage(
  # 引入React和ReactDOM(生产环境建议用压缩版)
  tags$script(src="https://unpkg.com/react@18/umd/react.development.js"),
  tags$script(src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"),
  # 嵌入React应用逻辑
  tags$script(HTML("
    const DashboardApp = () => {
      const [shinyData, setShinyData] = React.useState('');
      const [localInput, setLocalInput] = React.useState('');

      // 监听来自Shiny的消息
      React.useEffect(() => {
        const handler = Shiny.addCustomMessageHandler('shiny-to-react', (msg) => {
          setShinyData(msg.payload);
        });
        // 组件卸载时移除监听
        return () => Shiny.removeCustomMessageHandler('shiny-to-react');
      }, []);

      // 发送数据到Shiny后端
      const sendToShiny = () => {
        Shiny.setInputValue('react-to-shiny', localInput);
      };

      return (
        <div style={{padding: '20px'}}>
          <h3>React 交互区域</h3>
          <input 
            type='text' 
            value={localInput} 
            onChange={(e) => setLocalInput(e.target.value)}
            placeholder='输入内容发送到Shiny'
          />
          <button onClick={sendToShiny} style={{marginLeft: '10px'}}>发送</button>
          <p style={{marginTop: '15px'}}>来自Shiny的响应: {shinyData}</p>
        </div>
      );
    };

    ReactDOM.render(<DashboardApp />, document.getElementById('react-dashboard'));
  ")),
  # 挂载React应用的容器
  tags$div(id="react-dashboard"),
  # Shiny原生交互区域
  fluidRow(
    column(6,
      verbatimTextOutput("received_from_react"),
      actionButton("send_to_react", "发送消息到React")
    )
  )
)

server <- function(input, output, session) {
  # 接收React传来的数据并展示
  output$received_from_react <- renderPrint({
    req(input$`react-to-shiny`)
    paste("React发送的数据:", input$`react-to-shiny`)
  })

  # 点击按钮时发送数据到React
  observeEvent(input$send_to_react, {
    session$sendCustomMessage(
      type = "shiny-to-react",
      message = list(payload = paste("Shiny响应:", Sys.time()))
    )
  })
}

shinyApp(ui, server)

核心逻辑说明

  • React → Shiny:通过Shiny.setInputValue(key, value)将React中的数据注入Shiny的input对象,后端直接通过input$key读取。
  • Shiny → React:后端调用session$sendCustomMessage(type, message)发送自定义消息,前端通过Shiny.addCustomMessageHandler(type, callback)监听并处理消息。

二、封装React组件为Shiny自定义Input/Output

这种方式更贴合Shiny的原生开发模式,将React组件注册为Shiny的原生输入/输出控件,实现无缝双向绑定。

完整示例代码

前端React与Shiny绑定代码

// 注册自定义Shiny输入绑定
const ReactInputBinding = new Shiny.InputBinding();
$.extend(ReactInputBinding, {
  find: function(scope) {
    return $(scope).find(".react-shiny-input");
  },
  getValue: function(el) {
    // 从React组件实例获取当前值
    return el._reactInstance.state.inputValue;
  },
  setValue: function(el, newValue) {
    // 更新React组件的值
    el._reactInstance.setState({ inputValue: newValue });
  },
  subscribe: function(el, callback) {
    // 监听React组件值变化,触发Shiny更新
    el._reactInstance.onValueChange = function() {
      callback();
    };
  },
  unsubscribe: function(el) {
    delete el._reactInstance.onValueChange;
  }
});

Shiny.inputBindings.register(ReactInputBinding);

// 定义React输入组件
const ReactShinyInput = ({ initialValue }) => {
  const [inputValue, setInputValue] = React.useState(initialValue);

  React.useEffect(() => {
    // 将组件实例挂载到DOM元素,供绑定调用
    const domEl = document.getElementById("react-input");
    domEl._reactInstance = {
      state: { inputValue },
      setState: (newState) => {
        setInputValue(newState.inputValue);
      },
      onValueChange: null
    };

    // 值变化时触发Shiny的更新回调
    const handleChange = (e) => {
      setInputValue(e.target.value);
      if (domEl._reactInstance.onValueChange) {
        domEl._reactInstance.onValueChange();
      }
    };

    return () => delete domEl._reactInstance;
  }, []);

  return (
    <input
      type="text"
      value={inputValue}
      onChange={(e) => setInputValue(e.target.value)}
      className="react-shiny-input"
      id="react-input"
    />
  );
};

// 渲染组件到Shiny页面
ReactDOM.render(<ReactShinyInput initialValue="初始内容" />, document.getElementById("react-input-container"));

R Shiny 后端代码

library(shiny)

ui <- fluidPage(
  tags$script(src="https://unpkg.com/react@18/umd/react.development.js"),
  tags$script(src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"),
  tags$script(src="https://code.jquery.com/jquery-3.6.0.min.js"), # Shiny依赖jQuery
  tags$script(HTML("上面的JavaScript代码")),
  tags$div(id="react-input-container"),
  fluidRow(
    column(6,
      verbatimTextOutput("react_input_value"),
      textInput("update_react_input", "设置React输入框内容"),
      actionButton("update_btn", "更新")
    )
  )
)

server <- function(input, output, session) {
  # 读取自定义React输入的值
  output$react_input_value <- renderPrint({
    req(input$`react-input`)
    paste("React输入框当前值:", input$`react-input`)
  })

  # 更新React输入框的值
  observeEvent(input$update_btn, {
    session$sendInputMessage("react-input", list(value = input$update_react_input))
  })
}

shinyApp(ui, server)

核心逻辑说明

  • React → Shiny:通过自定义输入绑定的subscribe方法监听React组件值变化,触发Shiny的input更新,后端直接通过input$组件ID读取数据。
  • Shiny → React:后端调用session$sendInputMessage(组件ID, 数据),绑定的setValue方法会自动更新React组件的状态。

注意事项

  • 生产环境建议将React代码打包为静态文件(如用Vite或Create React App构建),再通过tags$script(src="path/to/bundle.js")引入,避免内嵌大量代码。
  • 传递复杂数据时,建议用JSON.stringify()和JSON.parse()进行序列化/反序列化,确保数据格式一致。
  • 若React应用规模较大,可考虑将Shiny作为API后端,React通过fetch调用Shiny的shiny::runApp启动的HTTP接口,实现更解耦的架构。

内容的提问来源于stack exchange,提问作者Janith Ravindu Rashmika

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 17:17:52