reactjs 如何从react with pyodide渲染一个python面板组件?

llmtgqce  于 2023-03-01  发布在  React
关注(0)|答案(2)|浏览(148)

我尝试使用panel文档中的一个例子来说明如何使用pyodide显示python中的panel组件,但是是来自react组件,而不是来自纯html。
我已经设置了一个可以克隆的minimal NextJS react app,并且只需要使用npm i && npm start就可以在本地运行,我的例子适用于返回字符串或数字的简单python代码,但是当我尝试将它与一个简单滑块的示例panel代码一起使用时,我不确定返回什么才能对渲染滑块做出React。
python代码包含在src/App.js中,我只是将面板代码中的myPythonCodeString变量重写为一个简单的1 + 9算法,以演示它在这种简单情况下的工作原理。
任何帮助都将不胜感激。

    • 编辑:**我已经向这个存储库添加了一些提交来解决这个问题,当这个问题被问到时,存储库的状态可以在提交3c735653dda0e873f17a98d0fb74edaca367ca00中看到。
v1l68za4

v1l68za41#

您的代码实际上是正确的,并且生成了小部件。唯一的问题是Helmet不像<head>那样同步加载脚本。因此,您的脚本最终将同时加载,并且由于它们相互依赖,加载将失败。
有一种简单的方法可以获得正确的输出:
1.将包含id="simple_app"的元素添加到应用程序中

root.render(
  <StrictMode>
    <App />
    <div id="simple_app">Replace this</div>
  </StrictMode>
);

1.注解Helmet中的每个脚本,pyodidebokeh除外
1.使用npm run start启动应用程序
1.取消注解下一个脚本并保存文件,使应用程序重新加载其状态
1.等待应用程序在浏览器中重新加载,然后使用下一个脚本重复操作
最后,所有脚本都将以正确的方式加载,小部件将继续工作。
最简单的解决方案是让Helmet同步加载脚本,或者使用其他方法加载脚本。

uidvcgyl

uidvcgyl2#

感谢@TachyonicBytes帮助解决这个问题。正如他们所说,有两个问题,一个是脚本需要同步加载,一个接一个按顺序加载,我使用useScript hook from the usehooks-ts library完成了这一点。另一个是我需要创建一个div,其id与python代码中面板组件中的servable目标的id匹配。
一个github repo和工作应用程序以及中的修正可以是viewed here
使用pyodide运行python代码的组件如下所示:

import React, { useEffect, useRef, useState } from "react";
import PropTypes from "prop-types";
import {useScript} from 'usehooks-ts'

/**
 * Pyodide component
 *
 * @param {object} props - react props
 * @param {string} props.pythonCode - python code to run
 * @param {string} [props.loadingMessage] - loading message
 * @param {string} [props.evaluatingMessage] - evaluating message
 * @returns {object} - pyodide node displaying result of python code
 */
function Pyodide({
  pythonCode,
  loadingMessage = "loading…",
  evaluatingMessage = "evaluating…",
}) {
  const pyodideStatus = useScript(`https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js`, {
    removeOnUnmount: false,
  })
  const bokehStatus = useScript(`https://cdn.bokeh.org/bokeh/release/bokeh-2.4.3.js`, {
      removeOnUnmount: false, shouldPreventLoad: pyodideStatus !== "ready"
  })
  const bokehWidgetsStatus = useScript(`https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.3.min.js`, {
    removeOnUnmount: false, shouldPreventLoad: bokehStatus !== "ready"
  })
  const bokehTablesStatus = useScript(`https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.3.min.js`, {
    removeOnUnmount: false, shouldPreventLoad: bokehWidgetsStatus !== "ready"
  })
  const panelStatus = useScript(`https://cdn.jsdelivr.net/npm/@holoviz/panel@0.14.0/dist/panel.min.js`, {
    removeOnUnmount: false, shouldPreventLoad: bokehTablesStatus !== "ready"
  })

  console.log(pyodideStatus, bokehStatus, bokehWidgetsStatus, bokehTablesStatus, panelStatus);

  const indexURL = "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/";
  const pyodide = useRef(null);
  const [isPyodideLoading, setIsPyodideLoading] = useState(true);
  const [pyodideOutput, setPyodideOutput] = useState(evaluatingMessage); // load pyodide wasm module and initialize it

  useEffect(() => {
    if (panelStatus === "ready") {
      setTimeout(()=>{
        (async function () {
          pyodide.current = await globalThis.loadPyodide({ indexURL });
          setIsPyodideLoading(false);
        })();
      }, 1000)
    }
  }, [pyodide, panelStatus]); // evaluate python code with pyodide and set output

  useEffect(() => {
    if (!isPyodideLoading) {
      const evaluatePython = async (pyodide, pythonCode) => {
        try {
          await pyodide.loadPackage("micropip");
          const micropip = pyodide.pyimport("micropip");
          await micropip.install("panel");
          return await pyodide.runPython(pythonCode);
        } catch (error) {
          console.error(error);
          return "Error evaluating Python code. See console for details.";
        }
      };
      (async function () {
        setPyodideOutput(await evaluatePython(pyodide.current, pythonCode));
      })();
    }
  }, [isPyodideLoading, pyodide, pythonCode]);

  if (panelStatus !== "ready") {
    return <div></div>
  }

  return (
    <>
      <div>
        {isPyodideLoading ? loadingMessage : pyodideOutput}
      </div>
    </>
  );
}

Pyodide.propTypes = {
  pythonCode: PropTypes.string.isRequired,
  loadingMessage: PropTypes.string,
  evaluatingMessage: PropTypes.string
};

export default Pyodide;

示例用法如下所示:

import Pyodide from "./pyodide";
import "./styles.css";

let myPythonCodeString = `
import panel as pn
pn.extension(sizing_mode="stretch_width")

slider = pn.widgets.FloatSlider(start=0, end=10, name='Amplitude')

def callback(new):
    return f'Amplitude is: {new}'

component = pn.Row(slider, pn.bind(callback, slider))
component.servable(target='my_panel_widget');
`;

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <Pyodide pythonCode={myPythonCodeString} />
      <div id="my_panel_widget"></div>
    </div>
  );
}

结果是:

相关问题