跳转到主要内容

适用于ipywidgets的React,无需额外操作

项目描述

ipyreact

适用于ipywidgets的React,无需webpack、npm或任何麻烦。只需编写jsx、tsx和python。

基于 AnyWidget 构建。

原因

Ipyreact增加了组合性,允许您向小部件添加子元素,这将在一个单独的React上下文中渲染整个react树,无需添加额外的div或创建新的react上下文。

这允许包装库,如 Material UIAnt Design 以及甚至 React-three-fiber

教程

本教程将指导您通过构建带有react的完整ipywidget的步骤。

JupyterLight Binder

只需点击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()

initial-30-fps-compressed

(注意:在录制中我们使用了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()

现在编辑、保存,并在你的浏览器/笔记本中查看更改。

hot-reload-compressed

(注意:在录制中我们使用了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包含点击次数)

magic-optimized

(注意:在录制中我们使用了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")
])

JupyterLight Binder

事件

可以通过事件参数传递事件。在这种情况下,onClick将被添加为按钮元素的prop。

import ipyreact
ipyreact.Widget(_type="button", children=["click me"], events={"onClick": print})

子类也可以添加一个event_onClick方法,它也将向props添加一个onClick事件处理程序。

JupyterLight Binder

导入外部模块

编写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 或其他模块的字典。默认情况下,我们支持 reactreact-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

在笔记本中测试: JupyterLight Binder

为 threejs 打包的 ESM 模块

请参阅此笔记本了解 3D WebGL threejs-fiber 示例

JupyterLight Binder

开发安装

创建开发环境

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标志。

项目详情


下载文件

下载适用于您的平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分布

ipyreact-0.4.2.tar.gz (1.9 MB 查看散列

上传时间

构建分布

ipyreact-0.4.2-py3-none-any.whl (1.3 MB 查看散列

上传时间 Python 3

支持者