solid

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

@json-render/solid

@json-render/solid

@json-render/solid
renders json-render specs into Solid component trees with fine-grained reactivity.
@json-render/solid
可将json-render规格渲染为具有细粒度响应式特性的Solid组件树。

Quick Start

快速开始

tsx
import { Renderer, JSONUIProvider } from "@json-render/solid";
import type { Spec } from "@json-render/solid";
import { registry } from "./registry";

export function App(props: { spec: Spec | null }) {
  return (
    <JSONUIProvider registry={registry} initialState={{}}>
      <Renderer spec={props.spec} registry={registry} />
    </JSONUIProvider>
  );
}
tsx
import { Renderer, JSONUIProvider } from "@json-render/solid";
import type { Spec } from "@json-render/solid";
import { registry } from "./registry";

export function App(props: { spec: Spec | null }) {
  return (
    <JSONUIProvider registry={registry} initialState={{}}>
      <Renderer spec={props.spec} registry={registry} />
    </JSONUIProvider>
  );
}

Create a Catalog

创建目录

typescript
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/solid/schema";
import { z } from "zod";

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",
    },
  },
  actions: {
    submit: { description: "Submit data" },
  },
});
typescript
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/solid/schema";
import { z } from "zod";

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",
    },
  },
  actions: {
    submit: { description: "Submit data" },
  },
});

Define Components

定义组件

Components receive
ComponentRenderProps
from the renderer:
ts
interface ComponentRenderProps<P = Record<string, unknown>> {
  element: UIElement<string, P>;
  children?: JSX.Element;
  emit: (event: string) => void;
  on: (event: string) => EventHandle;
  bindings?: Record<string, string>;
  loading?: boolean;
}
Example:
tsx
import type { BaseComponentProps } from "@json-render/solid";

export function Button(props: BaseComponentProps<{ label: string }>) {
  return (
    <button onClick={() => props.emit("press")}>{props.props.label}</button>
  );
}
组件会从渲染器接收
ComponentRenderProps
ts
interface ComponentRenderProps<P = Record<string, unknown>> {
  element: UIElement<string, P>;
  children?: JSX.Element;
  emit: (event: string) => void;
  on: (event: string) => EventHandle;
  bindings?: Record<string, string>;
  loading?: boolean;
}
示例:
tsx
import type { BaseComponentProps } from "@json-render/solid";

export function Button(props: BaseComponentProps<{ label: string }>) {
  return (
    <button onClick={() => props.emit("press")}>{props.props.label}</button>
  );
}

Create a Registry

创建注册表

typescript
import { defineRegistry } from "@json-render/solid";
import { catalog } from "./catalog";
import { Card } from "./Card";
import { Button } from "./Button";

const { registry, handlers, executeAction } = defineRegistry(catalog, {
  components: {
    Card,
    Button,
  },
  actions: {
    submit: async (params, setState, state) => {
      // custom action logic
    },
  },
});
typescript
import { defineRegistry } from "@json-render/solid";
import { catalog } from "./catalog";
import { Card } from "./Card";
import { Button } from "./Button";

const { registry, handlers, executeAction } = defineRegistry(catalog, {
  components: {
    Card,
    Button,
  },
  actions: {
    submit: async (params, setState, state) => {
      // custom action logic
    },
  },
});

Spec Structure

规格结构

json
{
  "root": "card1",
  "elements": {
    "card1": {
      "type": "Card",
      "props": { "title": "Hello" },
      "children": ["btn1"]
    },
    "btn1": {
      "type": "Button",
      "props": { "label": "Click me" },
      "on": {
        "press": { "action": "submit" }
      }
    }
  }
}
json
{
  "root": "card1",
  "elements": {
    "card1": {
      "type": "Card",
      "props": { "title": "Hello" },
      "children": ["btn1"]
    },
    "btn1": {
      "type": "Button",
      "props": { "label": "Click me" },
      "on": {
        "press": { "action": "submit" }
      }
    }
  }
}

Providers

提供者

  • StateProvider
    : state model read/write and controlled mode via
    store
  • VisibilityProvider
    : evaluates
    visible
    conditions
  • ValidationProvider
    : field validation +
    validateForm
    integration
  • ActionProvider
    : runs built-in and custom actions
  • JSONUIProvider
    : combined provider wrapper
  • StateProvider
    :通过
    store
    实现状态模型的读写与受控模式
  • VisibilityProvider
    :评估
    visible
    条件
  • ValidationProvider
    :字段验证 +
    validateForm
    集成
  • ActionProvider
    :运行内置与自定义操作
  • JSONUIProvider
    :组合式提供者包装器

Hooks

Hooks

  • useStateStore
    ,
    useStateValue
    ,
    useStateBinding
  • useVisibility
    ,
    useIsVisible
  • useActions
    ,
    useAction
  • useValidation
    ,
    useOptionalValidation
    ,
    useFieldValidation
  • useBoundProp
  • useUIStream
    ,
    useChatUI
  • useStateStore
    ,
    useStateValue
    ,
    useStateBinding
  • useVisibility
    ,
    useIsVisible
  • useActions
    ,
    useAction
  • useValidation
    ,
    useOptionalValidation
    ,
    useFieldValidation
  • useBoundProp
  • useUIStream
    ,
    useChatUI

Built-in Actions

内置操作

Handled automatically by
ActionProvider
:
  • setState
  • pushState
  • removeState
  • validateForm
ActionProvider
自动处理:
  • setState
  • pushState
  • removeState
  • validateForm

Dynamic Props and Bindings

动态属性与绑定

Supported expression forms include:
  • {"$state": "/path"}
  • {"$bindState": "/path"}
  • {"$bindItem": "field"}
  • {"$template": "Hi ${/user/name}"}
  • {"$computed": "fn", "args": {...}}
  • {"$cond": <condition>, "$then": <value>, "$else": <value>}
Use
useBoundProp
in components for writable bound values:
tsx
import { useBoundProp } from "@json-render/solid";

function Input(props: BaseComponentProps<{ value?: string }>) {
  const [value, setValue] = useBoundProp(
    props.props.value,
    props.bindings?.value,
  );
  return (
    <input
      value={String(value() ?? "")}
      onInput={(e) => setValue(e.currentTarget.value)}
    />
  );
}
useStateValue
,
useStateBinding
, and the
state
/
errors
/
isValid
fields from
useFieldValidation
are reactive accessors in Solid. Call them as functions inside JSX,
createMemo
, or
createEffect
.
支持的表达式形式包括:
  • {"$state": "/path"}
  • {"$bindState": "/path"}
  • {"$bindItem": "field"}
  • {"$template": "Hi ${/user/name}"}
  • {"$computed": "fn", "args": {...}}
  • {"$cond": <condition>, "$then": <value>, "$else": <value>}
在组件中使用
useBoundProp
获取可写入的绑定值:
tsx
import { useBoundProp } from "@json-render/solid";

function Input(props: BaseComponentProps<{ value?: string }>) {
  const [value, setValue] = useBoundProp(
    props.props.value,
    props.bindings?.value,
  );
  return (
    <input
      value={String(value() ?? "")}
      onInput={(e) => setValue(e.currentTarget.value)}
    />
  );
}
useStateValue
useStateBinding
以及
useFieldValidation
中的
state
/
errors
/
isValid
字段都是Solid中的响应式访问器。需在JSX、
createMemo
createEffect
中以函数形式调用它们。

Solid Reactivity Rules

Solid响应式规则

  • Do not destructure component props in function signatures when values need to stay reactive.
  • Keep changing reads inside JSX expressions,
    createMemo
    , or
    createEffect
    .
  • Context values are exposed through getter-based objects so consumers always observe live signals.
  • 当值需要保持响应式时,不要在函数签名中解构组件props。
  • 将变化的读取操作放在JSX表达式、
    createMemo
    createEffect
    中。
  • 上下文值通过基于getter的对象暴露,因此消费者始终能观察到实时信号。

Streaming UI

流式UI

tsx
import { useUIStream, Renderer } from "@json-render/solid";

const stream = useUIStream({ api: "/api/generate-ui" });
await stream.send("Create a support dashboard");

<Renderer
  spec={stream.spec}
  registry={registry}
  loading={stream.isStreaming}
/>;
Use
useChatUI
for chat + UI generation flows.
tsx
import { useUIStream, Renderer } from "@json-render/solid";

const stream = useUIStream({ api: "/api/generate-ui" });
await stream.send("Create a support dashboard");

<Renderer
  spec={stream.spec}
  registry={registry}
  loading={stream.isStreaming}
/>;
对于聊天+UI生成流程,可使用
useChatUI