适用于ipywidgets的React,无需额外操作
项目描述
ipyreact
适用于ipywidgets的React,无需webpack、npm或任何麻烦。只需编写jsx、tsx和python。
基于 AnyWidget 构建。
原因
Ipyreact增加了组合性,允许您向小部件添加子元素,这将在一个单独的React上下文中渲染整个react树,无需添加额外的div或创建新的react上下文。
这允许包装库,如 Material UI、Ant Design 以及甚至 React-three-fiber。
教程
本教程将指导您通过构建带有react的完整ipywidget的步骤。
只需点击JupyterLite或Binder链接即可开始交互式浏览。
目标
- 选择任何 Material UI示例,复制/粘贴代码,它应该在Jupyter Notebook、Jupyter Lab、Voila以及更具体的 Solara 中工作。
- 包装库,如 Ant Design,提供自定义任何JSON<->JavaScript对象(反)序列化的选项,例如使用内部dayjs对象的 DatePicker,该对象无法通过网络序列化到Python。
- 将小部件组合在一起,形成一个具有相同react上下文(例如useContext)的单个react树。
示例
内联代码
import ipyreact
class ConfettiWidget(ipyreact.ValueWidget):
_esm = """
import confetti from "canvas-confetti";
import * as React from "react";
export default function({value, setValue}) {
return <button onClick={() => confetti() && setValue(value + 1)}>
{value || 0} times confetti
</button>
};"""
ConfettiWidget()
(注意:在录制中我们使用了on_value,现在使用setValue)
热重载
创建一个tsx文件
// confetti.tsx
import confetti from "canvas-confetti";
import * as React from "react";
export default function ({ value, setValue }) {
return (
<button onClick={() => confetti() && setValue(value + 1)}>
{value || 0} times confetti
</button>
);
}
并在你的Python代码中使用它
import ipyreact
import pathlib
class ConfettiWidget(ipyreact.ValueWidget):
_esm = pathlib.Path("confetti.tsx")
ConfettiWidget()
现在编辑、保存,并在你的浏览器/笔记本中查看更改。
(注意:在录制中我们使用了on_value,现在使用setValue)
IPython魔法
首先加载ipyreact扩展
%load_ext ipyreact
然后使用%%react
魔法在你的笔记本中直接编写jsx/tsx
%%react
import confetti from "canvas-confetti";
import * as React from "react";
export default function({value, setValue}) {
return <button onClick={() => confetti() && setValue(value + 1)}>
{value || 0} times confetti
</button>
};
使用名称_last_react_widget
访问底层小部件(例如_last_react_widget.value
包含点击次数)
(注意:在录制中我们使用了on_value,现在使用setValue)
安装
你可以使用pip
进行安装
pip install ipyreact
用法
摘要
ValueWidget
有一个value
属性,它是一个traitlets.Any
属性。使用它将数据传递到你的react组件,或者从你的react组件获取数据(因为它从ipywidgets.ValueWidget继承,可以与ipywidgets的interact结合使用)。ipyreact.Widget
没有value
属性。- 所有属性都作为props添加到你的react组件中(例如上面示例中的
{value, setValue...}
对)。 - 对于每个属性
ipyreact
自动提供一个set<Traitname>
回调,你可以使用它从你的react组件设置属性值(例如上面示例中的setValue
)。(注意:我们之前使用了on_value
,现在已弃用) - 属性可以通过
Widget(props={"title": "My title"})
传递,并且与属性相反,不会将setTitle
可调用添加到属性中。 - 可以使用
Widget(children=['text', or_widget])
传递子元素,支持文本、小部件,以及ipyreact小部件的无缝渲染。 - 你的代码在前端使用sucrase进行转换,不需要打包器。
- 你的代码应该用ES模块编写。
- 设置
_debug=True
以在浏览器控制台中获取更多调试信息。 - 确保从你的模块中导出一个默认函数(例如
export default function MyComponent() { ... }
)。这是将被渲染的组件。 - 将
events={"onClick": handler}
传递给构造函数或添加一个名为event_onClick(self, data=None)
的方法,以将onClick
回调添加到你的props中。
HTML元素
你不需要提供模块代码来创建内置HTML元素,ipyreact支持与React的createElement相同的API,允许创建按钮等。
import ipyreact
ipyreact.Widget(_type="button", children=["click me"])
注意,除了所有原生浏览器元素外,也支持Web组件。
子元素
如上例所示,我们还支持子元素,它支持字符串列表(文本)、将作为不间断react树渲染的ipyreact.Widget
小部件,或任何其他ipywidgets
import ipyreact
import ipywidgets as widgets
ipyreact.Widget(_type="div", children=[
"normal text",
ipyreact.Widget(_type="button", children=["nested react widgets"]),
widgets.FloatSlider(description="regular ipywidgets")
])
事件
可以通过事件参数传递事件。在这种情况下,onClick
将被添加为按钮元素的prop。
import ipyreact
ipyreact.Widget(_type="button", children=["click me"], events={"onClick": print})
子类也可以添加一个event_onClick
方法,它也将向props添加一个onClick
事件处理程序。
导入外部模块
编写JSX代码而无需编译/打包是很好的,但使用外部库也同样好。
Ipyreact使用ES模块,允许以ES模块的形式导入外部库。在下面的示例中,我们使用https://esm.sh/,它将许多JS库作为ES模块暴露,我们可以直接导入。
import ipyreact
ipyreact.ValueWidget(
_esm="""
import confetti from "https://esm.sh/canvas-confetti@1.6.0";
import * as React from "react";
export default function({value, setValue}) {
return <button onClick={() => confetti() && setValue(value + 1)}>
{value || 0} times confetti
</button>
};
"""
)
导入映射
然而,上面的代码现在直接链接到"https://esm.sh/canvas-confetti@1.6.0",这使得代码非常特定于esm.sh。
为了解决这个问题,我们还支持使用 导入映射 来编写更独立于模块来源的代码。您可以使用 ipyreact.define_import_map
函数提供一个导入映射,该函数接受一个包含模块名称到 URL 或其他模块的字典。默认情况下,我们支持 react
和 react-dom
,它们是预打包的。
除了 react
之外,我们提供的默认设置是
define_import_map({
"@mui/material": "https://esm.sh/@mui/material@5.11.10?external=react,react-dom",
"@mui/material/": "https://esm.sh/@mui/material@5.11.10&external=react,react-dom/",
"@mui/icons-material/": "https://esm.sh/@mui/icons-material/?external=react,react-dom",
"canvas-confetti": "https://esm.sh/canvas-confetti@1.6.0",
})
请注意,对于基于 ReactJS 的库,添加 external=react,react-dom
非常重要,否则 esm.sh 将再次导入 ReactJS.
这意味着我们现在可以像这样编写我们的 ConfettiButton
import ipyreact
# note that this import_map is already part of the default
ipyreact.define_import_map({
"canvas-confetti": "https://esm.sh/canvas-confetti@1.6.0",
})
ipyreact.ValueWidget(
_esm="""
import confetti from "canvas-confetti";
import * as React from "react";
export default function({value, setValue}) {
return <button onClick={() => confetti() && setValue(value + 1)}>
{value || 0} times confetti
</button>
};
"""
)
这也意味着我们可以复制粘贴来自 mui 的 大多数 示例
%%react -n my_widget -d
import {Button} from "@mui/material";
import confetti from "canvas-confetti";
import * as React from "react";
export default function({ value, setValue}) {
console.log("value=", value);
return (
<Button
variant="contained"
onClick={() => confetti() && setValue(value + 1)}
>
{value || 0} times confetti
</Button>
);
}
我们使用 https://github.com/guybedford/es-module-shims 来浏览器页面实现导入映射功能。这也意味着尽管导入映射可以按小部件配置,但其配置是全局的。
打包的 ESM 模块
创建 ES 模块
虽然 esm.sh 使用方便,但在生产环境中,我们建议创建一个独立的打包。这将加载速度更快,并且不需要直接连接到 esm.sh,这在断网或防火墙环境中可能不可用。
我们不会为 https://ant.design/ 创建最小化打包
首先创建一个简单的文件,命名为 antd-minimal.js
,然后导出我们需要的部分。
export { Button, Flex, Slider } from "antd";
接下来,我们安装库
$ npm install antd
然后使用 ESBuild 将其转换为包含 react 的自包含模块/打包,因为我们已经有 ipyreact 提供了它。
$ npx esbuild ./antd-minimal.js --bundle --outfile=./antd-minimal.esm.js --format=esm --external:react --external:react-dom --target=esnext
现在我们可以用自定义名称定义模块(我们称其为 antd-minimal)。
import ipyreact
from pathlib import Path
ipyreact.define_module("antd-minimal", Path("./antd-minimal.esm.js"))
现在我们可以使用此模块的组件
def on_click(event_data):
w.children = ["Clicked"]
w = ipyreact.Widget(_module="antd-minimal", _type="Button", children=["Hi there"], events={"onClick": on_click})
w
或者,组合多个组件
stack = ipyreact.Widget(_module="antd-minimal", _type="Flex",
props={"vertical": True, "style": {"padding": "24px"}},
children=[
ipyreact.Widget(_module="antd-minimal", _type="Button", children=["Ant Design Button"]),
ipyreact.Widget(_module="antd-minimal", _type="Slider",
props={"defaultValue": 3, "min": 0, "max": 11}),
])
stack
输入组件可能需要一些自定义代码,并且需要继承 ValueWidget
。这通常意味着将值绑定到输入组件的正确属性上(在这种情况下,Slider 使用相同的名称 value
)并将事件处理器(在这种情况下为 onChange
)耦合到 setValue
函数。
import traitlets
class Slider(ipyreact.ValueWidget):
_esm = """
import * as React from "react";
import {Slider} from "antd-minimal"
export default ({value, setValue, ...rest}) => {
return <Slider value={value} onChange={(v) => setValue(v)} {...rest}/>
}
"""
s = Slider(value=2)
s
请注意,这取决于事件处理器的实现,值是直接传递,还是以数据作为参数传递的(合成的)事件。一个典型的事件处理器示例可以是 onChange={(event) => setValue(event.target.value)}
。
现在滑动条小部件是状态化的,并且我们通过 .value
特性实现了双向通信。例如,我们可以读取它
s.value
或者写入它,它将直接反映在 UI 中。
s.value = 10
为 threejs 打包的 ESM 模块
请参阅此笔记本了解 3D WebGL threejs-fiber 示例
开发安装
创建开发环境
conda create -n ipyreact-dev -c conda-forge nodejs yarn python 'jupyterlab<4'
conda activate ipyreact-dev
安装 python。这将构建 TS 包。
pip install -e ".[test, examples, dev]"
pre-commit install
当开发扩展时,您需要手动使用笔记本/实验室前端启用扩展。对于实验室,这是通过以下命令完成的
jupyter labextension develop --overwrite .
yarn run build
对于经典笔记本,您需要运行
jupyter nbextension install --sys-prefix --symlink --overwrite --py ipyreact
jupyter nbextension enable --sys-prefix --py ipyreact
请注意,--symlink
标志在 Windows 上不起作用,因此您在这里必须每次重建扩展时都运行 install
命令。对于某些安装,您可能还需要使用另一个标志代替 --sys-prefix
,但我们不会在这里介绍这些标志的含义。
二进制数据传输
例如,NumPy 数组或 Arrow 数据等二进制数据可以高效地传输到前端。属性支持支持缓冲区接口的对象。请参阅 此测试 作为示例。
如何查看您的更改
TypeScript
如果您使用JupyterLab进行开发,则可以在不同终端中同时监视源目录并运行JupyterLab,以监视扩展源代码的变化并自动重新构建小部件。
# Watch the source directory in one terminal, automatically rebuilding when needed
yarn run watch
# Run JupyterLab in another terminal
jupyter lab
更改后,请等待构建完成,然后刷新浏览器,更改应生效。
Python
如果您对Python代码进行了更改,则需要重新启动笔记本内核以使更改生效。
常见问题解答
您使用的是哪个版本的React。
我们目前仅支持React 18。虽然我们已经建立了一些脚手架来支持不同版本,但我们没有资金支持两个版本。
为什么ipyreact提供React?
如果需要将几个ReactJS组件组合成一个React应用程序,它们需要共享相同的React上下文。这使得React的useContext等特性在整个React树中工作成为可能。如果每个库都带来了自己的React,它们将无法通过这种方式进行通信。此外,每个子组件都需要嵌套在其自己的<div>
中,这可能会影响您应用程序的布局。当ipyreact提供React时,我们可以构建一个具有正常/真实React渲染树的真正的ReactJS应用程序。
我遇到了React错误
例如,如果您看到“无法读取null的属性(读取'useReducer')”,这意味着您正在加载自己的ReactJS版本。
如果您使用https://esh.sh,请确保在URL末尾添加??external=react,react-dom
,以确保您的esm捆绑包不包括自己的ReactJS版本,而是使用ipyreact提供的版本。
如果您使用esbuild自己构建捆绑包,请确保在CLI上添加--external:react --external:react-dom
标志。
项目详情
下载文件
下载适用于您的平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。