react by vercel-labs/json-render
npx skills add https://github.com/vercel-labs/json-render --skill react将 JSON 规范转换为 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";
// 使用属性模式创建目录
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>
),
},
});
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
React 模式使用元素树格式:
{
"root": {
"type": "Card",
"props": { "title": "Hello" },
"children": [
{ "type": "Button", "props": { "label": "Click me" } }
]
}
}
在元素上使用 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)。
| 提供者 | 用途 |
|---|---|
StateProvider | 在组件间共享状态(JSON 指针路径)。接受可选的 store 属性用于受控模式。 |
ActionProvider | 处理通过事件系统派发的动作 |
VisibilityProvider | 启用基于状态的条件渲染 |
ValidationProvider | 表单字段验证 |
将 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>
// 从任何地方进行变更 —— React 会自动重新渲染:
store.set("/count", 1);
当提供了 store 时,initialState 和 onStateChange 将被忽略。
任何属性值都可以是数据驱动的表达式,由渲染器在组件接收属性之前解析:
{ "$state": "/state/key" } - 从状态模型中读取(单向读取){ "$bindState": "/path" } - 双向绑定:从状态读取并支持写回。用于表单组件的自然值属性(value、checked、pressed 等)。{ "$bindItem": "field" } - 与重复项字段的双向绑定。在重复作用域内使用。{ "$cond":<condition>, "$then": <value>, "$else": <value> } - 条件值{ "$template": "Hello, ${/name}!" } - 将状态值插入字符串{ "$computed": "fn", "args": { ... } } - 使用已解析的参数调用已注册的函数{
"type": "Input",
"props": {
"value": { "$bindState": "/form/email" },
"placeholder": "Email"
}
}
组件不使用 statePath 属性进行双向绑定。请改用 { "$bindState": "/path" } 作为自然值属性。
组件接收的是已解析的属性。对于双向绑定的属性,请使用 useBoundProp 钩子以及渲染器提供的 bindings 映射。
通过 JSONUIProvider 或 createRenderer 上的 functions 属性注册 $computed 函数:
<JSONUIProvider
functions={{ fullName: (args) => `${args.first} ${args.last}` }}
>
组件使用 emit 来触发命名事件,或使用 on() 来获取带有元数据的事件句柄。元素的 on 字段将事件映射到动作绑定:
// 简单事件触发
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>
);
},
{
"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)指向变更路径。组件属性中的双向绑定在值属性上使用 { "$bindState": "/path" },而不是 statePath。
对于需要双向绑定的表单组件,当属性使用 { "$bindState": "/path" } 或 { "$bindItem": "field" } 时,请使用 useBoundProp 以及渲染器提供的 bindings 映射:
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 是已解析的属性;setValue 会写回绑定的状态路径(如果未绑定则无操作)。
用于构建不依赖于特定目录的可重用组件库(例如 @json-render/shadcn):
import type { BaseComponentProps } from "@json-render/react";
const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
<div>{props.title}{children}</div>
);
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 | 类型化的组件上下文(感知目录) |
每周安装量
613
代码仓库
GitHub 星标
13.3K
首次出现
2026年3月7日
安全审计
安装于
github-copilot595
codex482
cursor476
gemini-cli475
amp475
kimi-cli475
React renderer that converts JSON specs into React component trees.
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() }),
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>
),
},
});
The React schema uses an element tree format:
{
"root": {
"type": "Card",
"props": { "title": "Hello" },
"children": [
{ "type": "Button", "props": { "label": "Click me" } }
]
}
}
Use visible on elements to show/hide based on state. New syntax: { "$state": "/path" }, { "$state": "/path", "eq": value }, { "$state": "/path", "not": true }, { "$and": [cond1, cond2] } for AND, { "$or": [cond1, cond2] } for OR. Helpers: visibility.when("/path"), visibility.unless("/path"), visibility.eq("/path", val), visibility.and(cond1, cond2), visibility.or(cond1, cond2).
| Provider | Purpose |
|---|---|
StateProvider | Share state across components (JSON Pointer paths). Accepts optional store prop for controlled mode. |
ActionProvider | Handle actions dispatched via the event system |
VisibilityProvider | Enable conditional rendering based on state |
ValidationProvider | Form field validation |
Pass a StateStore to StateProvider (or JSONUIProvider / createRenderer) to use external state management (Redux, Zustand, XState, etc.):
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 store is provided, initialState and onStateChange are ignored.
Any prop value can be a data-driven expression resolved by the renderer before components receive props:
{ "$state": "/state/key" } - reads from state model (one-way read)
{ "$bindState": "/path" } - two-way binding: reads from state and enables write-back. Use on the natural value prop (value, checked, pressed, etc.) of form components.
{ "$bindItem": "field" } - two-way binding to a repeat item field. Use inside repeat scopes.
{ "$cond":<condition>, "$then": <value>, "$else": <value> } - conditional value
{ "$template": "Hello, ${/name}!" } - interpolates state values into strings
{ "$computed": "fn", "args": { ... } } - calls registered functions with resolved args
Components do not use a statePath prop for two-way binding. Use { "$bindState": "/path" } on the natural value prop instead.
Components receive already-resolved props. For two-way bound props, use the useBoundProp hook with the bindings map the renderer provides.
Register $computed functions via the functions prop on JSONUIProvider or createRenderer:
<JSONUIProvider
functions={{ fullName: (args) => `${args.first} ${args.last}` }}
>
Components use emit to fire named events, or on() to get an event handle with metadata. The element's on field maps events to action bindings:
// 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" } }
}
The EventHandle returned by on() has: emit(), shouldPreventDefault (boolean), and bound (boolean).
Elements can declare a watch field (top-level, sibling of type/props/children) to trigger actions when state values change:
{
"type": "Select",
"props": { "value": { "$bindState": "/form/country" }, "options": ["US", "Canada"] },
"watch": { "/form/country": { "action": "loadCities" } },
"children": []
}
The setState, pushState, removeState, and validateForm actions are built into the React schema and handled automatically by ActionProvider. They are injected into AI prompts without needing to be declared in catalog 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 validates all registered fields and writes { valid, errors } to state.
Note: statePath in action params (e.g. setState.statePath) targets the mutation path. Two-way binding in component props uses { "$bindState": "/path" } on the value prop, not statePath.
For form components that need two-way binding, use useBoundProp with the bindings map the renderer provides when a prop uses { "$bindState": "/path" } or { "$bindItem": "field" }:
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) returns [value, setValue]. The value is the resolved prop; setValue writes back to the bound state path (no-op if not bound).
For building reusable component libraries not tied to a specific catalog (e.g. @json-render/shadcn):
import type { BaseComponentProps } from "@json-render/react";
const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
<div>{props.title}{children}</div>
);
defineRegistry conditionally requires the actions field only when the catalog declares actions. Catalogs with actions: {} can omit it.
| Export | Purpose |
|---|---|
defineRegistry | Create a type-safe component registry from a catalog |
Renderer | Render a spec using a registry |
schema | Element tree schema (includes built-in state actions: setState, pushState, removeState, validateForm) |
useStateStore | Access state context |
useStateValue | Get single value from state |
useBoundProp |
Weekly Installs
613
Repository
GitHub Stars
13.3K
First Seen
Mar 7, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
github-copilot595
codex482
cursor476
gemini-cli475
amp475
kimi-cli475
agent-browser 浏览器自动化工具 - Vercel Labs 命令行网页操作与测试
136,300 周安装
{ "type": "Input", "props": { "value": { "$bindState": "/form/email" }, "placeholder": "Email" } }
Two-way binding for $bindState/$bindItem expressions |
useActions | Access actions context |
useAction | Get a single action dispatch function |
useOptionalValidation | Non-throwing variant of useValidation (returns null if no provider) |
useUIStream | Stream specs from an API endpoint |
createStateStore | Create a framework-agnostic in-memory StateStore |
StateStore | Interface for plugging in external state management |
BaseComponentProps | Catalog-agnostic base type for reusable component libraries |
EventHandle | Event handle type (emit, shouldPreventDefault, bound) |
ComponentContext | Typed component context (catalog-aware) |