mcpField
R
react

react

github.com/vercel-labs开发18.8k16.6k 次安装5 天前更新
代码仓库

关于 react

@json-render/react 是一个 React 渲染器,可将 JSON 元素树规格转换为 React 组件树,面向构建数据驱动界面的开发者。它提供类型安全的组件注册表,以及状态绑定、条件显示、事件和表单验证能力。

主要功能

  1. 01将 JSON 规格渲染为 React 组件树
  2. 02定义目录和类型安全组件注册表
  3. 03支持命名插槽及条件显示
  4. 04提供状态读取与双向绑定
  5. 05处理事件、动作和状态监听
  6. 06支持表单字段验证

适用场景

  1. 01按 JSON 规格构建动态界面
  2. 02创建可复用的目录组件
  3. 03将表单输入绑定到应用状态
  4. 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 与同一容器上的 $item visible 条件配合使用,只渲染符合条件的项:{ "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获取单个操作分发函数
useOptionalValidationuseValidation 的非抛错版本(如果没有提供程序,则返回 null)
useUIStream从 API 端点流式传输规范
createStateStore创建与框架无关的内存 StateStore
StateStore用于接入外部状态管理的接口
BaseComponentProps用于可复用组件库、与目录无关的基础类型
EventHandle事件句柄类型(emit、shouldPreventDefault、bound)
ComponentContext类型化组件上下文(感知目录)

评论

支持 Markdown