react
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese@json-render/react
@json-render/react
React renderer that converts JSON specs into React component trees.
可将JSON规范转换为React组件树的React渲染器。
Quick Start
快速开始
typescript
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} />;
}typescript
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} />;
}Creating a Catalog
创建组件目录
typescript
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() }),
description: "Card container with title",
},
},
});
// 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>
),
},
});typescript
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react/schema";
import { defineRegistry } from "@json-render/react";
import { z } from "zod";
// 基于属性schema创建组件目录
export const catalog = defineCatalog(schema, {
components: {
Button: {
props: z.object({
label: z.string(),
variant: z.enum(["primary", "secondary"]).nullable(),
}),
description: "可点击的按钮",
},
Card: {
props: z.object({ title: z.string() }),
description: "带标题的卡片容器",
},
},
});
// 定义类型安全属性的组件实现
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>
),
},
});Spec Structure (Element Tree)
规范结构(元素树)
The React schema uses an element tree format:
json
{
"root": {
"type": "Card",
"props": { "title": "Hello" },
"children": [
{ "type": "Button", "props": { "label": "Click me" } }
]
}
}React schema使用元素树格式:
json
{
"root": {
"type": "Card",
"props": { "title": "Hello" },
"children": [
{ "type": "Button", "props": { "label": "Click me" } }
]
}
}Visibility Conditions
可见性条件
Use on elements to show/hide based on state. New syntax: , , , for AND, for OR. Helpers: , , , , .
visible{ "$state": "/path" }{ "$state": "/path", "eq": value }{ "$state": "/path", "not": true }{ "$and": [cond1, cond2] }{ "$or": [cond1, cond2] }visibility.when("/path")visibility.unless("/path")visibility.eq("/path", val)visibility.and(cond1, cond2)visibility.or(cond1, cond2)在元素上使用属性,可基于状态显示/隐藏元素。新增语法:、、,逻辑与用,逻辑或用。辅助方法:、、、、。
visible{ "$state": "/path" }{ "$state": "/path", "eq": value }{ "$state": "/path", "not": true }{ "$and": [cond1, cond2] }{ "$or": [cond1, cond2] }visibility.when("/path")visibility.unless("/path")visibility.eq("/path", val)visibility.and(cond1, cond2)visibility.or(cond1, cond2)Providers
提供者
| Provider | Purpose |
|---|---|
| Share state across components (JSON Pointer paths). Accepts optional |
| Handle actions dispatched via the event system |
| Enable conditional rendering based on state |
| Form field validation |
| 提供者 | 用途 |
|---|---|
| 跨组件共享状态(JSON Pointer路径)。支持可选 |
| 处理通过事件系统派发的动作 |
| 启用基于状态的条件渲染 |
| 表单字段验证 |
External Store (Controlled Mode)
外部状态存储(受控模式)
Pass a to (or / ) to use external state management (Redux, Zustand, XState, etc.):
StateStoreStateProviderJSONUIProvidercreateRenderertsx
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);When is provided, and are ignored.
storeinitialStateonStateChange向(或 / )传入即可使用外部状态管理工具(Redux、Zustand、XState等):
StateProviderJSONUIProvidercreateRendererStateStoretsx
import { createStateStore, type StateStore } from "@json-render/react";
const store = createStateStore({ count: 0 });
<StateProvider store={store}>{children}</StateProvider>
// 可在任意位置修改状态 —— React会自动重新渲染:
store.set("/count", 1);当提供了时,和会被忽略。
storeinitialStateonStateChangeDynamic Prop Expressions
动态属性表达式
Any prop value can be a data-driven expression resolved by the renderer before components receive props:
- - reads from state model (one-way read)
{ "$state": "/state/key" } - - two-way binding: reads from state and enables write-back. Use on the natural value prop (value, checked, pressed, etc.) of form components.
{ "$bindState": "/path" } - - two-way binding to a repeat item field. Use inside repeat scopes.
{ "$bindItem": "field" } - - conditional value
{ "$cond": <condition>, "$then": <value>, "$else": <value> } - - interpolates state values into strings
{ "$template": "Hello, ${/name}!" } - - calls registered functions with resolved args
{ "$computed": "fn", "args": { ... } }
json
{
"type": "Input",
"props": {
"value": { "$bindState": "/form/email" },
"placeholder": "Email"
}
}Components do not use a prop for two-way binding. Use on the natural value prop instead.
statePath{ "$bindState": "/path" }Components receive already-resolved props. For two-way bound props, use the hook with the map the renderer provides.
useBoundPropbindingsRegister functions via the prop on or :
$computedfunctionsJSONUIProvidercreateRenderertsx
<JSONUIProvider
functions={{ fullName: (args) => `${args.first} ${args.last}` }}
>任意属性值都可以是数据驱动的表达式,渲染器会在组件接收属性前完成解析:
- - 从状态模型读取数据(单向读取)
{ "$state": "/state/key" } - - 双向绑定:从状态读取数据并支持回写。用于表单组件的原生值属性(value、checked、pressed等)。
{ "$bindState": "/path" } - - 双向绑定到循环项字段,在循环作用域内使用。
{ "$bindItem": "field" } - - 条件取值
{ "$cond": <condition>, "$then": <value>, "$else": <value> } - - 将状态值插值到字符串中
{ "$template": "Hello, ${/name}!" } - - 调用注册的函数并传入解析后的参数
{ "$computed": "fn", "args": { ... } }
json
{
"type": "Input",
"props": {
"value": { "$bindState": "/form/email" },
"placeholder": "邮箱"
}
}组件无需使用属性实现双向绑定,直接在原生值属性上使用即可。
statePath{ "$bindState": "/path" }组件接收到的是已经解析完成的属性。对于双向绑定的属性,可结合渲染器提供的映射使用 hook。
bindingsuseBoundProp可通过或的属性注册函数:
JSONUIProvidercreateRendererfunctions$computedtsx
<JSONUIProvider
functions={{ fullName: (args) => `${args.first} ${args.last}` }}
>Event System
事件系统
Components use to fire named events, or to get an event handle with metadata. The element's field maps events to action bindings:
emiton()ontsx
// 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>
);
},json
{
"type": "Button",
"props": { "label": "Submit" },
"on": { "press": { "action": "submit" } }
}The returned by has: , (boolean), and (boolean).
EventHandleon()emit()shouldPreventDefaultbound组件可使用触发命名事件,或使用获取带元数据的事件句柄。元素的字段将事件映射到动作绑定:
emiton()ontsx
// 简单事件触发
Button: ({ props, emit }) => (
<button onClick={() => emit("press")}>{props.label}</button>
),
// 带元数据的事件句柄(例如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>
);
},json
{
"type": "Button",
"props": { "label": "提交" },
"on": { "press": { "action": "submit" } }
}on()EventHandleemit()shouldPreventDefaultboundState Watchers
状态监听器
Elements can declare a field (top-level, sibling of type/props/children) to trigger actions when state values change:
watchjson
{
"type": "Select",
"props": { "value": { "$bindState": "/form/country" }, "options": ["US", "Canada"] },
"watch": { "/form/country": { "action": "loadCities" } },
"children": []
}元素可声明字段(与type/props/children同级的顶级字段),在状态值变化时触发动作:
watchjson
{
"type": "Select",
"props": { "value": { "$bindState": "/form/country" }, "options": ["US", "加拿大"] },
"watch": { "/form/country": { "action": "loadCities" } },
"children": []
}Built-in Actions
内置动作
The , , , and actions are built into the React schema and handled automatically by . They are injected into AI prompts without needing to be declared in catalog :
setStatepushStateremoveStatevalidateFormActionProvideractionsjson
{ "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 }Note: in action params (e.g. ) targets the mutation path. Two-way binding in component props uses on the value prop, not .
statePathsetState.statePath{ "$bindState": "/path" }statePathsetStatepushStateremoveStatevalidateFormActionProvideractionsjson
{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "新建" } } }
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }
{ "action": "validateForm", "params": { "statePath": "/formResult" } }validateForm{ valid, errors }注意:动作参数中的(例如)指向的是修改路径。组件属性的双向绑定是在值属性上使用,而非。
statePathsetState.statePath{ "$bindState": "/path" }statePathuseBoundProp
useBoundProp
For form components that need two-way binding, use with the map the renderer provides when a prop uses or :
useBoundPropbindings{ "$bindState": "/path" }{ "$bindItem": "field" }tsx
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]valuesetValue对于需要双向绑定的表单组件,当属性使用了或时,可结合渲染器提供的映射使用:
{ "$bindState": "/path" }{ "$bindItem": "field" }bindingsuseBoundProptsx
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]valuesetValueBaseComponentProps
BaseComponentProps
For building reusable component libraries not tied to a specific catalog (e.g. ):
@json-render/shadcntypescript
import type { BaseComponentProps } from "@json-render/react";
const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
<div>{props.title}{children}</div>
);用于构建不绑定特定组件目录的可复用组件库(例如):
@json-render/shadcntypescript
import type { BaseComponentProps } from "@json-render/react";
const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
<div>{props.title}{children}</div>
);defineRegistry
defineRegistry
defineRegistryactionsactions: {}仅当组件目录声明了actions时,才会要求传入字段。如果目录的则可以省略该字段。
defineRegistryactionsactions: {}Key Exports
核心导出项
| Export | Purpose |
|---|---|
| Create a type-safe component registry from a catalog |
| Render a spec using a registry |
| Element tree schema (includes built-in state actions: setState, pushState, removeState, validateForm) |
| Access state context |
| Get single value from state |
| Two-way binding for |
| Access actions context |
| Get a single action dispatch function |
| Non-throwing variant of useValidation (returns null if no provider) |
| Stream specs from an API endpoint |
| Create a framework-agnostic in-memory |
| Interface for plugging in external state management |
| Catalog-agnostic base type for reusable component libraries |
| Event handle type ( |
| Typed component context (catalog-aware) |
| 导出项 | 用途 |
|---|---|
| 基于组件目录创建类型安全的组件注册表 |
| 使用注册表渲染规范 |
| 元素树schema(包含内置状态动作:setState、pushState、removeState、validateForm) |
| 访问状态上下文 |
| 从状态中获取单个值 |
| 为 |
| 访问动作上下文 |
| 获取单个动作派发函数 |
| 非抛出异常版的useValidation(如果没有提供者则返回null) |
| 从API接口流式获取规范 |
| 创建框架无关的内存级 |
| 接入外部状态管理的接口 |
| 可复用组件库的无目录依赖基础类型 |
| 事件句柄类型( |
| 类型化组件上下文(感知组件目录) |