ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

lowcode-engine 中用 @alilc/lowcode-react-renderer 实现列表渲染:loop 循环、dataHandler 数据源与分页实践

lowcode-engine 中用 @alilc/lowcode-react-renderer 实现列表渲染:loop 循环、dataHandler 数据源与分页实践 lowcode-engine 中用 alilc/lowcode-react-renderer 实现列表渲染loop 循环、dataHandler 数据源与分页实践【免费下载链接】lowcode-engineAn enterprise-class low-code technology stack with scale-out design / 一套面向扩展设计的企业级低代码技术体系项目地址: https://gitcode.com/GitHub_Trending/lo/lowcode-engine本篇围绕packages/react-renderer/demo/list.md这份官方 Demo 展开讲解如何通过alilc/lowcode-react-renderer把一个包含列表循环、数据源处理和分页交互的页面 Schema 渲染成 React 视图。读完本文你将掌握loop循环渲染与this.item作用域的原理、dataSource.dataHandler本地数据源的配置方式、this.page.reloadDataSource()分页刷新链路以及components/appHelper等渲染器入参的完整用法。一、Demo 要解决什么问题低代码引擎产出的页面不是手写的 React 代码而是一份 JSON 化的 Schema节点树 props 数据源 事件函数。运行时的核心任务是给定一份 Schema交给渲染器把组件树翻译成真实的 React 元素。packages/react-renderer正是为 React 技术栈提供的这个运行时入口其包描述为react renderer for ali lowcode engine见 package.json。demo/list.md演示的是一个非常典型的业务场景文章列表页—— 顶部是若干条带标题、时间、标签、点赞/喜爱/评论数的卡片底部是一个分页器。它一次性覆盖了 Schema 驱动的四个核心能力loop属性驱动的列表循环渲染模板表达式{{...}}在 props 与 children 中的取值dataSource.dataHandler声明式本地数据源事件函数onChange中调用this.page.reloadDataSource()刷新状态。二、渲染入口ReactRenderer 的三个核心入参Demo 的完整代码如下摘自 list.mdimport React, { PureComponent } from react; import ReactDOM from react-dom; import ReactRenderer from alilc/lowcode-react-renderer; import schema from ./schemas/list; import components from ./config/components/index; import utils from ./config/utils; import constants from ./config/constants; class Demo extends PureComponent { static displayName renderer-demo; render() { return ( div classNamedemo ReactRenderer key{schema.fileName} schema{schema} components{components} appHelper{{ utils, constants }} / /div ); } } ReactDOM.render(( Demo / ), mountNode);三个入参各司其职入参本 Demo 的取值作用schemaschemas/list.js页面模型描述整棵组件树、数据源与事件componentsconfig/components/index.js组件映射表componentName字符串到真实 React 组件的映射appHelper{ utils, constants }应用级助手对象可在 Schema 的 JS 表达式中通过this.utils/this.constants访问另外key{schema.fileName}利用了 Schema 根节点上的fileName: tab_article在页面模型切换时强制重建渲染器实例。components 映射表内置组件 第三方组件库Demo 的组件映射表由两部分组成config/components/index.js四个自研基础组件Div、A、Text、Image分别位于demo/config/components/目录下从alifd/next引入的整组 Fusion 组件并通过解构补全子组件const { Item: TabItem } Tab、const { Column: TableColumn } Table、const { Row, Col } Grid等最终导出一个包含Button、Pagination、Tab、Table等约 80 个条目的对象。这个映射表就是 Schema 中componentName字段如Div、Text、Button、Pagination能解析成真实组件的原因。渲染器在render阶段会把内置渲染器PageRenderer等与业务components合并const allComponents { ...RENDERER_COMPS, ...components }; let Comp allComponents[componentName] || RENDERER_COMPS[${componentName}Renderer];对应实现见 renderer-core 的 renderer.tsx 中的getComp。appHelperutils 与 constantsconfig/utils.js 导出了一个含Message、moment以及自定义方法test(msg)的对象其中test内部通过this.Message.notice(msg)访问同对象属性——这说明appHelper里的方法会以该对象为this上下文被调用。config/constants.js 则是常量表{ name: renderer-demo }。二者最终挂在ReactRenderer的appHelperprop 上Schema 内的 JS 表达式可通过this.utils.Message.notice(...)、this.constants.name使用。三、列表 Schema 逐层拆解list.js 的根节点与列表关键片段如下节选export default { componentName: Page, fileName: tab_article, props: { style: { paddingTop: 20, paddingRight: 20, paddingLeft: 20 } }, children: [ { componentName: Div, props: { style: { marginTop: 5, marginBottom: 15, borderBottom: 1px solid rgba(244,244,244) } }, children: [ // 标题行左侧标题 右侧时间 { componentName: Div, children: [ { componentName: Text, props: { text: {{this.item.title}}, style: { color: rgba(51,51,51) } } }, { componentName: Text, props: { text: {{this.item.datetime}}, style: { fontSize: 12px, color: #666, float: right } } } ] }, // 描述区children 直接用表达式 { componentName: Div, props: { style: { paddingBottom: 15, fontSize: 13px, color: #666 } }, children: {{this.item.description}} }, // 标签区嵌套 loop 渲染 tags右侧还有点赞/喜爱/评论三个统计 { componentName: Div, children: [ { componentName: Button, props: { type: normal, size: small, style: { marginRight: 5, marginLeft: 5 } }, children: {{this.item}}, loop: {{this.item.tags}} }, { componentName: Div, props: { style: { marginBottom: 15, float: right } }, children: [ { componentName: Div, children: {{点赞this.item.star}}, /* ...省略两个同类节点 */ }, ] } ] } ], loop: {{this.state.dataSource}} }, // 分页器 { componentName: Pagination, props: { shape: normal, type: normal, size: medium, style: { marginTop: 10, marginBottom: 30, textAlign: right }, onChange: function onChange(current, e) { // 页码发生改变时的回调函数 // param {Number} current 改变后的页码数 // param {Object} e 点击事件对象 this.page.reloadDataSource(); } } } ], // 本地数据源 dataSource: { dataHandler: function dataHandler(dataMap) { const dataSource [ /* 5 条 { title, description, tags, datetime, star, like, comment } 数据 */ ]; return { dataSource }; } } };可以从这份 Schema 中提炼出四个可复用的模式1.loop循环一条列表模板 × N 份数据卡片外层 Div 声明了loop: {{this.state.dataSource}}。渲染器解析该表达式得到一个数组后会按数组长度把整棵子树复制 N 份每份复制体各自绑定一个循环项。本例中每份卡片里的this.item.title、this.item.datetime取的就是dataSource数组中对应那篇文章的字段。嵌套循环也完全支持标签 Button 上的loop: {{this.item.tags}}在文章循环内部又展开了一层标签循环。此时内层this.item已被重绑定为标签字符串直播、大促、简介所以children: {{this.item}}直接把标签文本渲染出来。2. 表达式即数据绑定Schema 里所有{{...}}字符串都是 JS 表达式JSExpression在每次渲染时被求值text: {{this.item.title}}—— props 级绑定children: {{this.item.description}}—— children 级绑定children: {{点赞this.item.star}}—— 表达式内还演示了字符串拼接与字符串字面量。this指向当前渲染作用域循环体内是item/index所在的循环作用域循环外则沿原型链向上找到页面级上下文因此this.state.dataSource、this.page在任意表达式里都可用。3.dataHandler无后端的本地数据源页面dataSource里只声明了dataHandler它同步返回{ dataSource: [...] }渲染器会把返回值合并进组件state于是循环表达式this.state.dataSource才能取到数组。这个模式适合纯静态/模拟数据若需要真实请求可改用dataSource.list声明 fetch 数据源DataHelper对 fetch/jsonp 的支持见>if (schema.loop ! null) { const loop this.__parseData(schema.loop, scope); if (Array.isArray(loop) loop.length 0) return null; const useLoop isUseLoop(loop, this.__designModeIsDesign); if (useLoop) { return this.__createLoopVirtualDom({ ...schema, loop }, scope, parentInfo, idx); } }表达式先经__parseData求值成数组空数组直接不渲染——这是列表页无数据即空白的默认行为is-use-loop.ts 区分了两种模式渲染模式下只要loop是数组就展开设计模式下要求数组非空loop.length 0注释写明是为了防止无法设计的情况——编辑器画布需要至少一条数据才能选中、编辑节点。展开逻辑在__createLoopVirtualDombase.tsxconst itemArg (schema.loopArgs schema.loopArgs[0]) || DEFAULT_LOOP_ARG_ITEM; // 默认 item const indexArg (schema.loopArgs schema.loopArgs[1]) || DEFAULT_LOOP_ARG_INDEX; // 默认 index return loop.map((item, i) { const loopSelf: any { [itemArg]: item, [indexArg]: i }; loopSelf.__proto__ scope; return this.__createVirtualDom({ ...schema, loop: undefined, // 防止无限递归 props: { ...schema.props, // 循环下 key 不能为常量这样会造成 key 值重复渲染异常 key: isJSExpression(schema.props?.key) ? schema.props?.key : null, }, }, loopSelf, parentInfo, idx ? ${idx}_${i} : i); });三个细节值得注意作用域链loopSelf只含item/index两个键且__proto__指向外层scope所以this.item.xxx取循环项、this.state依旧能穿透到页面状态——这正是嵌套循环中this.item被重绑定而this.state保持不变的底层原因key 保护若props.key是常量会被置空避免 React key 重复警告源码注释原话即循环下 key 不能为常量这样会造成 key 值重复渲染异常自定义循环参数名Schema 可用loopArgs: [article, no]把this.item换成this.articleDemo 未使用该字段走的是默认的item/index。4.3 reloadDataSource 的完整链路Demo 中this.page.reloadDataSource()触发的调用链为BaseRenderer.componentDidMount首次调用this.reloadDataSource()base.tsx即页面挂载后自动执行一遍数据源__initDataSourcebase.tsx根据 Schema 初始化数据源若appHelper.requestHandlersMap存在则走独立的数据源引擎alilc/lowcode-datasource-engine/interpret否则实例化DataHelper处理dataSource.dataHandler/list本 Demo 无requestHandlersMap走DataHelper分支reloadDataSource内部执行this.__dataHelper.getInitData()把返回结果本例即{ dataSource: [...] }setState合并进组件状态列表表达式this.state.dataSource随之重新求值整棵循环子树刷新。this.__dataHelper.getInitData() .then((res) { if (isEmpty(res)) { this.forceUpdate(); return resolve({}); } this.setState(res, resolve as () void); }) .catch((err) reject(err));4.4 事件函数与 this 上下文onChange: function (current, e) { this.page.reloadDataSource(); }这类内嵌函数由渲染器解析后以组件上下文为this调用executeLifeCycleMethod与表达式解析均依赖thisRequiredInJSE这一默认开启的选项见 renderer.tsx 的 defaultPropsthisRequiredInJSE: true。这保证了 Schema 中的回调可以像写在组件内部一样访问this.page、this.state、this.utils等成员。五、最小可用模板与适用边界把 Demo 抽象后一个列表 分页页面的最小 Schema 骨架是{ componentName: Page, children: [ { componentName: 你的卡片容器, children: [ /* 用 {{this.item.xxx}} 描述单条卡片 */ ], loop: {{this.state.list}} }, { componentName: Pagination, props: { onChange: function (current) { this.page.reloadDataSource(); } } } ], dataSource: { dataHandler: function () { return { list: /* 当前页数据 */ [] }; } } }使用时需明确以下前提与限制根节点componentName必须是Page、Block、Component之一或Div否则渲染器直接报错模型结构异常renderer.tsxSchema 中出现的每个componentName都必须在components映射表中注册否则降级为NotFoundComponent提示文本loop表达式求值结果必须是数组空数组不渲染任何节点需要暂无数据占位时应通过condition或外层组件自行处理本 Demo 使用ReactDOM.render与 React 16 风格的类组件写法devDependencies 中为react^16.4.1在 React 18 环境中需自行替换为createRoot用法渲染器 API 本身不变。六、小结demo/list.md虽短却完整覆盖了运行时渲染的四个关键机制loop 作用域原型链实现列表与嵌套循环、{{...}}JSExpression实现数据绑定、dataSource.dataHandler实现声明式本地数据源、Schema 内嵌事件函数通过this.page.reloadDataSource()完成分页刷新。配合 renderer-core 中的__createLoopVirtualDom、isUseLoop、__initDataSource等实现可以完整还原从 Schema 到 React 虚拟 DOM 的执行路径。同目录下的 table.md、dataSource.md、i18n.md、compose.md 分别扩展了表格、远程数据源、国际化与复合组件场景可作为本文的延伸读物。【免费下载链接】lowcode-engineAn enterprise-class low-code technology stack with scale-out design / 一套面向扩展设计的企业级低代码技术体系项目地址: https://gitcode.com/GitHub_Trending/lo/lowcode-engine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表