Skip to main content

自定义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 组件代码需遵循以下约定:

  1. 必须 export default 一个 React 组件,组件通过 props 接收参数。
  2. 无需 import React:JSX 使用自动运行时(automatic runtime)编译,直接书写 JSX 即可;如需使用 React.useState 等 API,可直接引用 React(已隐式注入),无需导入。
  3. 支持 TypeScript 语法:TS 类型标注、泛型等均可直接书写。
  4. 组件 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;
dom 参数与容器场景

当把自定义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 组件代码调用的平台能力方法如下:

方法签名说明
requestGetsdk.requestGet<T>(url: string, params?: Record<string, any>): Promise<T>调用平台 API 发起 GET 请求,params 为查询参数,返回平台响应模型
requestPostsdk.requestPost<T>(url: string, data?: any): Promise<T>调用平台 API 发起 POST 请求,data 作为请求体提交,返回平台响应模型
requestPostWithLoadingsdk.requestPostWithLoading<T>(url: string, data?: any, loadingOptions?: WithLoadingOptions<T>): Promise<T>带自动 Loading 的 POST 请求,loading 的显示与关闭自动处理,默认自动展示服务端返回的消息提示
isMobilesdk.isMobile(): boolean返回当前设备是否为移动端(true 移动端 / false 桌面端),与宿主应用的设备识别同源

requestPostWithLoading 的 loadingOptions 可选项:

参数类型说明
loadingTextstringLoading 提示文本,默认「操作中...」
durationnumberLoading 自动关闭时间(秒),默认取系统配置
showDefaultNotificationboolean是否显示默认的服务端消息通知,默认 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 使用以下组件库(均为宿主应用内置版本,无需安装):

模块名说明官方地址
reactReact 核心 API(useState、useEffect、useRef、useMemo 等)react.dev
react-domReact DOM API(createPortal 等)react.dev/reference/react-dom
antdAnt Design 组件库全部组件与工具方法ant.design
antd-mobileAnt Design Mobile 移动端组件库全部组件mobile.ant.design
@ant-design/iconsAnt 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
classnamesclassName 条件拼接工具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 错误。

使用建议​

  1. 需要条件渲染、事件交互、状态管理等复杂逻辑的自定义 UI 时,优先使用本组件;仅静态展示内容用自定义HTML更简单。
  2. 平台数据一律通过 sdk 在组件内部获取,组件保存后随页面自动运行,普通用户无需任何配置即可看到渲染结果。
  3. 组件代码完全由开发者负责,请确保代码可正常编译与运行;错误会被组件边界捕获并就地展示,不会拖垮宿主页面。

下一步​

  • 自定义HTML - HTML/JavaScript 字符串方式的轻量自定义渲染
  • 微组件 - 嵌入完整的外部微应用(qiankun)
  • 页面 - 返回平台页面总览

属性明细​

以下内容面向技术实现与 AI 检索。

自定义React组件属性表​

属性名类型含义取值/存储值
ScriptstringReact 组件源代码代码文本,默认空字符串;必须 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