@lhx-kit/renderer配置驱动 UI 渲染器。支持 Vue3 和 React 双绑定。
给它一个 JSON schema,它吐出一个可渲染的组件。
把**"一个页面的 layout + visibility + data binding"** 固化成声明式结构——改文案 / 开关 / 布局不用改代码、不用发版。
电商 / 内容 / 运营类产品的真实生存状态:
传统做法:每次改动 → 改代码 → PR → CI → 发版。整个链路几小时到几天。
把结构性变化(组件有哪些、按什么顺序、在什么条件下可见、传什么 props)从代码里抽出来,变成 JSON。
运行时由渲染器解读 JSON,组件库里查对应 component 实现。
| 问题 | 说明 |
|---|---|
| 🌐 每个平台要写自己的渲染器 | Web / 小程序 / Native 各自实现 |
| 🧩 无法引用业务组件 | <Banner> 需要用户先注册组件类,渲染器必须知道注册表 |
| 🎨 和设计稿距离远 | 设计师:"这里是个 Banner";开发:"这里是个 div 嵌套 span" |
| 问题 | 说明 |
|---|---|
| 🔄 全量 re-render | 状态变化 → 重算字符串 → innerHTML 重写 |
| 📈 性能差 | 每次都要全量 patch |
| 🎯 事件绑定困难 | 字符串里没有 React/Vue 的事件系统 |
| 🧩 不易组件化 | 嵌套插槽 / 作用域插槽实现复杂 |
"name": "Banner")指向用户预先注册的组件实现——渲染器不关心具体框架{"$": "path.to.value"} 声明——不用 eval,不依赖 Function 构造器,CSP 友好输出 RenderNode[],和具体框架无关:
:::danger 为什么不用 new Function
unsafe-evalnew Function 让他们任意执行代码
:::我们的做法:只支持"根路径 + 属性访问":
:::tip 有意的限制 如果你需要复杂逻辑,把它放进 state 里作为预计算的字段。
不让 JSON 变成"另一门编程语言"。
支持的操作符:
| 操作符 | 语义 |
|---|---|
and |
所有子条件为真 |
or |
任意子条件为真 |
not |
取反 |
eq / neq |
深等于 / 深不等于 |
gt / gte / lt / lte |
数值比较 |
exists |
值不是 null / undefined |
"user.age >= 18 && flags.region === 'CN'" 这种写法好处是短,但:
mergeSchema(base, patches) 支持三种 op:
合并顺序:
每一层都可以修正前面的结果。
:::tip 典型用法
resolveVariant(variants, ctx) 从上到下找第一个 when 为真的条目返回。没匹配返回 null → 回落到 options.schema。
渲染时:
| 特性 | 说明 |
|---|---|
| 超时控制 | 原生 fetch + AbortController |
| zod 校验 | 远端 schema 始终校验(独立 entry @lhx-kit/renderer/schema-zod) |
| 失败 fallback | 失败时返回 options.schema |
| 诊断回调 | 所有错误通过 onDiagnostic 上报,不抛异常 |
zod 压缩后 54KB,gzip 12KB,不小。
默认情况下我们不在客户端加载 zod:
:::info validate 默认 false 的依据
import schema from './render.json' 时已经被 TS 类型检查过(PageSchema 类型),运行时 zod 是纯 overheadfetchRemoteSchema 内部始终校验,和 validate 无关
:::结果:
validate: true → schema-zod 被 Rollup 标记为"仅动态 import 可达" → 独立 chunk,默认不加载:::tip 这也是 性能章节 保留那个 sub-10KB chunk 的原因 合进主 bundle 就失去了 lazy 效果。
内部实现:
defineComponent + h() 函数式渲染slots API 传递{click: ...} → onClick)<Suspense> 包裹 lazy component内部实现:
useState 存 schema stateuseEffect 处理 remote fetch / patches merge 的异步流{click: ...} → onClick)<Comp headerSlot={[node1, node2]}>从 0.1 版起,renderer schema 不放在 src/schemas/,而是和页面代码同目录:
lhx-cli add page <name> 会自动生成这一份 render.json 模板。页面代码直接 import schema from './render.json'。
src/schemas/ 再改| 依赖 | 在哪用 | 为什么 |
|---|---|---|
zod |
schema-zod.ts |
运行时校验;懒加载,默认不进 bundle |
react / vue |
peer dependencies | 渲染目标框架,用户项目自己带 |
:::warning 下面场景用普通 JSX / SFC 更合适
lhx-kit 的 validate: false 默认 + 懒加载设计就是表达这个态度:renderer 是可选能力,不用也没成本。
examples/vmpa/src/pages/*/render.json