关于 react
@json-render/react 是一个 React 渲染器,可将 JSON 元素树规格转换为 React 组件树,面向构建数据驱动界面的开发者。它提供类型安全的组件注册表,以及状态绑定、条件显示、事件和表单验证能力。
主要功能
- 01将 JSON 规格渲染为 React 组件树
- 02定义目录和类型安全组件注册表
- 03支持命名插槽及条件显示
- 04提供状态读取与双向绑定
- 05处理事件、动作和状态监听
- 06支持表单字段验证
适用场景
- 01按 JSON 规格构建动态界面
- 02创建可复用的目录组件
- 03将表单输入绑定到应用状态
- 04根据状态变化触发界面动作
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vercel-labs/ json-render --skill react安装后在 Claude Code 中输入/react确认已加载。
详细介绍
@json-render/react
可将 JSON 规范转换为 React 组件树的 React 渲染器。
快速开始
import { defineRegistry, Renderer } from "@json-render/react";
import { catalog } from "./catalog";
const { registry } = defineRegistry(catalog, {
components: {
Card: ({ props, children }) => <div>{props.title}{children}</div>,
},
});
function App({ spec }) {
return <Renderer spec={spec} registry={registry} />;
}
创建目录
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react/schema";
import { defineRegistry } from "@json-render/react";
import { z } from "zod";
// Create catalog with props schemas
export const catalog = defineCatalog(schema, {
components: {
Button: {
props: z.object({
label: z.string(),
variant: z.enum(["primary", "secondary"]).nullable(),
}),
description: "Clickable button",
},
Card: {
props: z.object({ title: z.string() }),
slots: ["default"],
description: "Card container with title",
},
Layout: {
props: z.object({}),
slots: ["default", "header", "footer"],
description: "Layout with named content regions",
},
},
});
// Define component implementations with type-safe props
const { registry } = defineRegistry(catalog, {
components: {
Button: ({ props }) => (
<button className={props.variant}>{props.label}</button>
),
Card: ({ props, children }) => (
<div className="card">
<h2>{props.title}</h2>
{children}
</div>
),
Layout: ({ children, slots }) => (
<div>
<header>{slots?.header}</header>
<main>{children}</main>
<footer>{slots?.footer}</footer>
</div>
),
},
});
规范结构(元素树)
React schema 使用元素树格式:
{
"root": {
"type": "Card",
"props": { "title": "Hello" },
"children": [{ "type": "Button", "props": { "label": "Click me" } }]
}
}
命名插槽
使用 children 表示 "default" 插槽。对于目录声明的其他插槽名称,使用元素顶层的 slots 对象:
{
"type": "Layout",
"props": {},
"children": ["main"],
"slots": {
"header": ["heading"],
"footer": ["actions"]
}
}
注册表组件通过 slots?.header、slots?.footer 等属性接收命名内容。请勿使用 slots.default。
可见性条件
在元素上使用 visible,根据状态显示或隐藏元素。新语法:{ "$state": "/path" }、{ "$state": "/path", "eq": value }、{ "$state": "/path", "not": true }、{ "$and": [cond1, cond2] } 表示 AND、{ "$or": [cond1, cond2] } 表示 OR。辅助函数:visibility.when("/path")、visibility.unless("/path")、visibility.eq("/path", val)、visibility.and(cond1, cond2)、visibility.or(cond1, cond2)。
Provider
| Provider | 用途 |
|---|---|
StateProvider | 在组件间共享状态(JSON Pointer 路径)。可选接受用于受控模式的 store 属性。 |
ActionProvider | 处理通过事件系统派发的操作 |
VisibilityProvider | 根据状态启用条件渲染 |
ValidationProvider | 表单字段校验 |
外部 Store(受控模式)
将 StateStore 传递给 StateProvider(或 JSONUIProvider / createRenderer),以使用外部状态管理(Redux、Zustand、XState 等):
import { createStateStore, type StateStore } from "@json-render/react";
const store = createStateStore({ count: 0 });
<StateProvider store={store}>{children}</StateProvider>;
// Mutate from anywhere — React re-renders automatically:
store.set("/count", 1);
提供 store 时,initialState 和 onStateChange 会被忽略。
动态 Prop 表达式
任何 prop 值都可以是数据驱动表达式,渲染器会在组件接收 props 前对其求值:
{ "$state": "/state/key" }- 从状态模型读取(单向读取){ "$bindState": "/path" }- 双向绑定:从状态读取并启用回写。在表单组件的自然值 prop(value、checked、pressed 等)上使用。{ "$bindItem": "field" }- 双向绑定到 repeat 项字段。在 repeat 作用域内使用。- 筛选列表:将
repeat与同一容器上的$itemvisible 条件配合使用,只渲染符合条件的项:{ "repeat": { "statePath": "/tasks", "key": "id" }, "visible": { "$item": "status", "eq": "todo" }, "children": ["task-card"] }。由 AND 组合的$state条件控制容器外壳;$item/$index条件用于筛选项。 - 嵌套列表:在 repeat 内部,使用
{ "repeat": { "statePath": { "$item": "comments" }, "key": "id" } }遍历外层项中的数组。 { "$cond": <condition>, "$then": <value>, "$else": <value> }- 条件值{ "$template": "Hello, ${/name}!" }- 将状态值插入字符串{ "$computed": "fn", "args": { ... } }- 使用求值后的参数调用已注册函数
{
"type": "Input",
"props": {
"value": { "$bindState": "/form/email" },
"placeholder": "Email"
}
}
组件不使用 statePath prop 进行双向绑定。请在自然值 prop 上使用 { "$bindState": "/path" }。
组件接收的 props 已完成求值。对于双向绑定的 prop,请使用 useBoundProp hook 以及渲染器提供的 bindings 映射。
通过 JSONUIProvider 或 createRenderer 的 functions prop 注册 $computed 函数:
<JSONUIProvider
functions={{ fullName: (args) => `${args.first} ${args.last}` }}
>
事件系统
组件使用 emit 触发命名事件,或使用 on() 获取带有元数据的事件句柄。元素的 on 字段将事件映射到操作绑定:
// Simple event firing
Button: ({ props, emit }) => (
<button onClick={() => emit("press")}>{props.label}</button>
),
// Event handle with metadata (e.g. preventDefault)
Link: ({ props, on }) => {
const click = on("click");
return (
<a href={props.href} onClick={(e) => {
if (click.shouldPreventDefault) e.preventDefault();
click.emit();
}}>{props.label}</a>
);
},
{
"type": "Button",
"props": { "label": "Submit" },
"on": { "press": { "action": "submit" } }
}
on() 返回的 EventHandle 包含:emit()、shouldPreventDefault(布尔值)和 bound(布尔值)。
状态监听器
元素可以声明 watch 字段(顶层字段,与 type/props/children 同级),以便在状态值发生变化时触发操作:
{
"type": "Select",
"props": {
"value": { "$bindState": "/form/country" },
"options": ["US", "Canada"]
},
"watch": { "/form/country": { "action": "loadCities" } },
"children": []
}
内置操作
setState、pushState、removeState 和 validateForm 操作内置于 React 架构中,并由 ActionProvider 自动处理。它们会注入 AI 提示词,无需在目录的 actions 中声明:
{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "New" } } }
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }
{ "action": "validateForm", "params": { "statePath": "/formResult" } }
validateForm 会验证所有已注册的字段,并将 { valid, errors } 写入状态。
注意:操作参数中的 statePath(例如 setState.statePath)用于指定变更路径。组件 props 中的双向绑定使用值 prop 上的 { "$bindState": "/path" },而不是 statePath。
useBoundProp
对于需要双向绑定的表单组件,当 prop 使用 { "$bindState": "/path" } 或 { "$bindItem": "field" } 时,请使用渲染器提供的 bindings 映射,并配合 useBoundProp:
import { useBoundProp } from "@json-render/react";
Input: ({ element, bindings }) => {
const [value, setValue] = useBoundProp<string>(
element.props.value,
bindings?.value
);
return (
<input
value={value ?? ""}
onChange={(e) => setValue(e.target.value)}
/>
);
},
useBoundProp(propValue, bindingPath) 返回 [value, setValue]。value 是解析后的 prop;setValue 会写回绑定的状态路径(如果未绑定则不执行任何操作)。
BaseComponentProps
用于构建不绑定到特定目录的可复用组件库(例如 @json-render/shadcn):
import type { BaseComponentProps } from "@json-render/react";
const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
<div>{props.title}{children}</div>
);
defineRegistry
只有当目录声明了操作时,defineRegistry 才会要求提供 actions 字段。actions: {} 的目录可以省略该字段。
主要导出
| 导出 | 用途 |
|---|---|
defineRegistry | 根据目录创建类型安全的组件注册表 |
Renderer | 使用注册表渲染规范 |
schema | 元素树架构(包含内置状态操作:setState、pushState、removeState、validateForm) |
useStateStore | 访问状态上下文 |
useStateValue | 从状态中获取单个值 |
useBoundProp | 为 $bindState/$bindItem 表达式提供双向绑定 |
useActions | 访问操作上下文 |
useAction | 获取单个操作分发函数 |
useOptionalValidation | useValidation 的非抛错版本(如果没有提供程序,则返回 null) |
useUIStream | 从 API 端点流式传输规范 |
createStateStore | 创建与框架无关的内存 StateStore |
StateStore | 用于接入外部状态管理的接口 |
BaseComponentProps | 用于可复用组件库、与目录无关的基础类型 |
EventHandle | 事件句柄类型(emit、shouldPreventDefault、bound) |
ComponentContext | 类型化组件上下文(感知目录) |