自定义React组件(React)
自定义React组件支持在页面中在线编写 React 组件代码并在浏览器中实时编译渲染,适合需要复杂交互逻辑与组件化 UI 的自定义展示场景。
概述
自定义React组件(React 自定义组件 / React Widget)与「自定义HTML」一样属于自定义渲染类组件,区别在于它编写的是真正的 React 组件,而不是 HTML 字符串:
| 对比项 | 自定义HTML | 自定义React组件 |
|---|---|---|
| 代码形态 | HTML 字符串或返回字符串的函数 | export default 导出的 React 组件 |
| 事件交互 | HTML 模式不支持事件绑定 | 完整支持 React 事件、状态(State) |
| 平台能力调用 | 数据源接口 + 占位符 | 代码内通过平台 SDK(sdk)直接调用 |
| 实时预览 | 支持(JavaScript 模式除外) | 支持,左代码右预览 |
组件在浏览器内实时 编译(Babel,支持 JSX 与 TypeScript 语法),开发者保存后页面直接运行,无需构建发布。
配置项
将「自定义React组件」拖入画布后,在属性抽屉中配置:
| 配置项 | 说明 |
|---|---|
| 自定义代码 | 点击「编辑代码」打开在线编辑器弹窗,编写 React 组件代码,必填 |
编辑器弹窗为左右布局:左侧为 Monaco 代码编辑器,右侧为实时预览区(输入停顿约 0.4 秒后自动编译渲染);中间分割条可拖拽调节两侧宽度(每侧有最小宽度限制)。弹窗占页面约 90% 宽度,点击蒙层或按 ESC 不会关闭,仅通过「确定」(保存并关闭)或「取消」按钮关闭,避免未保存代码丢失。
编写React代码
React 组件代码需遵循以下约定:
- 必须
export default一个 React 组件,组件通过props接收参数。 - 无需
import React:JSX 使用自动运行时(automatic runtime)编译,直接书写 JSX 即可;如需使用React.useState等 API,可直接引用React(已隐式注入),无需导入。 - 支持 TypeScript 语法:TS 类型标注、泛型等均可直接书写。
- 组件 props 固定顺序为
{ sdk, data, dom }:sdk:平台 SDK 对象,通过其方法调用平台能力(见下节);data:预留的业务数据字典,当前自定义React组件未传入数据源,取值为undefined;dom:传入的内部节点组件(React.ReactNode)。当自定义React组件被用作容器时,宿主会通过dom传入内部节点,组件代码决定将其渲染到自身布局的哪个位置。
基础示例:
function Widget(props) {
const { sdk, data, dom } = props;
const [count, setCount] = React.useState(0);
return (
<Button type="primary" onClick={() => setCount(count + 1)}>
点击次数:{count}
</Button>
);
}
export default Widget;
当把自定义React组件用作页面中的容器部件时,宿主会通过 dom 传入内部节点组件,组件代码直接渲染 {dom} 即可,并可自由控制其外层布局与包裹结构:
function Widget(props) {
const { sdk, data, dom } = props;
return (
<div style={{ padding: 12, border: '1px solid #eee' }}>
<h4>自定义容器标题</h4>
{dom /* 内部节点渲染在这里 */}
</div>
);
}
export default Widget;
与 JavaScript 模式返回 HTML 字符串不同,React 组件返回的是 JSX 元素。若代码存在语法错误或运行时错误,错误信息会直接显示在右侧预览区(编辑时)或组件区域内(页面运行时),不会影响页面其他部分。
平台SDK(sdk)方法列表
props.sdk 是平台注入的 SDK 对象,开放给 React 组件代码调用的平台能力方法如下:
| 方法 | 签名 | 说明 |
|---|---|---|
requestGet | sdk.requestGet<T>(url: string, params?: Record<string, any>): Promise<T> | 调用平台 API 发起 GET 请求,params 为查询参数,返回平台响应模型 |
requestPost | sdk.requestPost<T>(url: string, data?: any): Promise<T> | 调用平台 API 发起 POST 请求,data 作为请求体提交,返回平台响应模型 |
requestPostWithLoading | sdk.requestPostWithLoading<T>(url: string, data?: any, loadingOptions?: WithLoadingOptions<T>): Promise<T> | 带自动 Loading 的 POST 请求,loading 的显示与关闭自动处理,默认自动展示服务端返回的消息提示 |
isMobile | sdk.isMobile(): boolean | 返回当前设备是否为移动端(true 移动端 / false 桌面端),与宿主应用的设备识别同源 |
requestPostWithLoading 的 loadingOptions 可选项:
| 参数 | 类型 | 说明 |
|---|---|---|
loadingText | string | Loading 提示文本,默认「操作中...」 |
duration | number | Loading 自动关闭时间(秒),默认取系统配置 |
showDefaultNotification | boolean | 是否显示默认的服务端消息通知,默认 true |
onSuccess | (response: T) => void | 成功回调,提供后不再显示默认成功消息 |
onError | (error: any) => void | 失败回调 |
sdk.requestGet 示例
查询参数通过 params 传递,返回平台响应模型(res.data 为业务数据):
function Widget(props) {
const { sdk } = props;
const [users, setUsers] = React.useState([]);
React.useEffect(() => {
sdk.requestGet('/api/User/GetUserList', { page: 1, pageSize: 20 })
.then(res => setUsers(res.data ?? []));
}, []);
return <div>{users.length} 个用户</div>;
}
export default Widget;
sdk.requestPost 示例
请求体通过第二个参数直接提交,适合查询类接口与静默提交:
function Widget(props) {
const { sdk } = props;
const [list, setList] = React.useState([]);
const loadData = () => {
sdk.requestPost('/api/MessageTodo/GetMessageList', {
current: 1,
pageSize: 15,
condition: { Field: 'IsRead', Operator: 'eq', Value: false },
}).then(res => setList(res.data ?? []));
};
React.useEffect(() => { loadData(); }, []);
return <div>{list.map(item => <div key={item.Id}>{item.Title}</div>)}</div>;
}
export default Widget;
sdk.requestPostWithLoading 示例
适合保存、提交审批等操作类请求,请求期间自动显示 Loading,成功后自动提示服务端消息:
function Widget(props) {
const { sdk } = props;
const submit = () => {
sdk.requestPostWithLoading('/api/Order/Submit', { orderId: 1001 }, {
loadingText: '正在提交订单...',
onSuccess: () => {
// 成功后的自定义逻辑,如刷新列表、关闭弹窗等
},
});
};
return <Button type="primary" onClick={submit}>提交订单</Button>;
}
export default Widget;
sdk.isMobile 示例
同步方法,直接返回布尔值,用于移动端与桌面端的差异化渲染:
function Widget(props) {
const { sdk } = props;
return (
<div>
{sdk.isMobile()
? <List size="small" /> {/* 移动端紧凑布局 */}
: <Table size="small" />} {/* 桌面端表格布局 */}
</div>
);
}
export default Widget;
与自定义HTML 不同,自定义React组件不需要配置数据源接口。需要平台数据时,直接在代码中通过 sdk.requestGet / sdk.requestPost / sdk.requestPostWithLoading 调用平台接口;后续更多平台能力(消息、跳转、用户上下文等)也会以 sdk 方法的形式持续扩展。
可用组件库
React 组件代码中可以通过 import 使用以下组件库(均为宿主应用内置版本,无需安装):
| 模块名 | 说明 | 官方地址 |
|---|---|---|
react | React 核心 API(useState、useEffect、useRef、useMemo 等) | react.dev |
react-dom | React DOM API(createPortal 等) | react.dev/reference/react-dom |
antd | Ant Design 组件库全部组件与工具方法 | ant.design |
antd-mobile | Ant Design Mobile 移动端组件库全部组件 | mobile.ant.design |
@ant-design/icons | Ant Design 图标库全部图标组件 | ant.design/components/icons |
react-device-detect | 设备检测库(isMobile、isMobileOnly、isTablet 等),与 sdk.isMobile() 同源 | github.com/duskload/react-device-detect |
dayjs | 日期时间处理库 | day.js.org |
moment | 日期时间处理库 | momentjs.com |
lodash | 工具函数库 | lodash.com |
classnames | className 条件拼接工具 | github.com/JedWatson/classnames |
导入示例:
import { Button, Space, Tag } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
import { cloneDeep } from 'lodash';
function Widget(props) {
return (
<Space>
<Button icon={<SearchOutlined />}>搜索</Button>
<Tag color="blue">{dayjs().format('YYYY-MM-DD')}</Tag>
</Space>
);
}
export default Widget;
import 只能使用上表中的模块名,导入白名单之外的模块会在运行时抛出 Module "xxx" is not available in the React widget runtime 错误。
使用建议
- 需要条件渲染、事件交互、状态管理等复杂逻辑的自定义 UI 时,优先使用本组件;仅静态展示内容用自定义HTML更简单。
- 平台数据一律通过
sdk在组件内部获取,组件保存后随页面自动运行,普通用户无需任何配置即可看到渲染结果。 - 组件代码完全由开发者负责,请确保代码可正常编译与运行;错误会被组件边界捕获并就地展示,不会拖垮宿主页面。
下一步
属性明细
以下内容面向技术实现与 AI 检索。
自定义React组件属性表
| 属性名 | 类型 | 含义 | 取值/存储值 |
|---|---|---|---|
Script | string | React 组件源代码 | 代码文本,默认空字符串;必须 export default 一个组件,编译目标为浏览器内 Babel 转译(JSX 自动运行时 + TypeScript) |
GetSidebarItemMeta() 默认写入 Script(空字符串)。与自定义HTML 不同,本组件没有 RenderMode 与 MetaInterfaceId 属性,数据获取由代码内 sdk 调用承担。uid 固定为 e8f4b6c9-2a7d-4d3e-9f1b-5c8a0d6e3f2a,widgetType 为 React。
组件运行时注入
| 项 | 说明 |
|---|---|
| 组件 props | { sdk, data, dom },data 预留(当前为 undefined),sdk 为平台 SDK 实例,dom 为容器场景下传入的内部节点(React.ReactNode) |
| 可导入模块 | 见「可用组件库」,运行时按模块名从注册表解析,白名单外模块抛错 |
| JSX 运行时 | automatic runtime,编译产物引用 react/jsx-runtime,无需 import React |
| 隐式注入 | 工厂函数隐式注入 React,代码可直接引用 React.xxx |
存储示例
组件通常位于列(Column)内,以下为嵌套在行(Row)→ 列(Column)中的完整存储结构:
{
"id": "a7b8c9d0-0001-4a2b-8c3d-1234567890ab",
"uid": "5a0f3c1e-9b8d-4f6a-9c2e-7d1b4a8e6f3d",
"dragType": "Row",
"originalType": "Row",
"widgetType": "Row",
"widgetTypeName": "基础容器",
"meta": {
"ColumnSpace": "5"
},
"children": [
{
"id": "a7b8c9d0-0002-4a2b-8c3d-1234567890ab",
"uid": "7b9a905e-83d4-452b-83c8-694a3e0935e3",
"dragType": "Column",
"originalType": "Column",
"widgetType": "Column",
"widgetTypeName": "布局列组件",
"meta": {
"LayoutType": "vertical",
"Align": "default",
"ItemSpace": "5"
},
"children": [
{
"id": "a7b8c9d0-0003-4a2b-8c3d-1234567890ab",
"uid": "e8f4b6c9-2a7d-4d3e-9f1b-5c8a0d6e3f2a",
"dragType": "Widget",
"originalType": "Widget",
"widgetType": "React",
"widgetTypeName": "自 定义React组件",
"meta": {
"Script": "import { Button } from 'antd';\n\nfunction Widget(props) {\n return <Button type=\"primary\">Hello</Button>;\n}\n\nexport default Widget;"
},
"children": []
}
]
}
]
}
异常与校验
| 场景 | 行为 |
|---|---|
| 代码编译失败(语法/类型错误) | 错误信息以 Alert 形式展示在编辑器右侧预览区(编辑时)或组件区域内(页面运行时),不渲染组件 |
| 组件渲染抛错 | 由 React 错误边界捕获,组件区域内就地展示错误提示,宿主页面不受影响 |
| 事件回调 / 异步调用抛错 | 页面运行时同样被错误边界兜底,不产生页面级崩溃 |
| 导入白名单外模块 | 运行时抛出 Module "xxx" is not available in the React widget runtime |
权限与限制
- 组件代码由开发者账号在页面设计器中编辑,普通用户仅能看到代码渲染结果,无法查看或修改代码。
import仅支持「可导入的组件库」白名单,不 支持动态加载其他 npm 包。- 代码在浏览器内实时编译,首次渲染包含编译耗时;组件保存后编译结果按源码缓存,同一段代码不重复编译。
相关实现
| 内容 | 位置 |
|---|---|
| 组件定义(编辑态/用户态/配置面板) | web/src/components/Widgets/UiWidgets/ReactWidget.tsx |
| 在线编辑器(左代码右预览、拖拽分割、错误拦截) | web/src/components/CommonCmps/ReactRender/ReactEditor.tsx |
| 运行时渲染器(编译挂载、错误边界) | web/src/components/CommonCmps/ReactRender/ReactRenderer.tsx |
| 浏览器内编译(Babel 转译 + 工厂执行 + 编译缓存) | web/src/components/CommonCmps/ReactRender/reactCompiler.ts |
| 可导入模块注册表 | web/src/components/CommonCmps/ReactRender/reactModules.ts |
平台 SDK(requestGet / requestPost / requestPostWithLoading / isMobile) | web/src/components/CommonCmps/ReactRender/ReactWidgetSdk.ts |