关于 svelte
@json-render/svelte 是用于 Svelte 5 的 json-render 渲染器,可将规范转换为 Svelte 组件树。它面向构建动态界面的开发者,支持状态、事件、验证及流式 UI。
主要功能
- 01将 UI 规范渲染为组件树
- 02通过目录和注册表定义组件
- 03支持状态驱动的可见性
- 04支持事件与内置动作
- 05支持动态属性和双向绑定
- 06流式渲染 UI 与聊天界面
适用场景
- 01将 JSON 规范呈现为 Svelte 界面
- 02根据状态控制元素显示
- 03将组件事件绑定到动作
- 04逐步呈现生成中的界面
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ vercel-labs/ json-render --skill svelte安装后在 Claude Code 中输入/svelte确认已加载。
详细介绍
@json-render/svelte
可将 json-render 规范转换为 Svelte 组件树的 Svelte 5 渲染器。
快速入门
<script lang="ts">
import { Renderer, JsonUIProvider } from "@json-render/svelte";
import type { Spec } from "@json-render/svelte";
import Card from "./components/Card.svelte";
import Button from "./components/Button.svelte";
interface Props {
spec: Spec | null;
}
let { spec }: Props = $props();
const registry = { Card, Button };
</script>
<JsonUIProvider>
<Renderer {spec} {registry} />
</JsonUIProvider>
创建目录
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/svelte";
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 with title",
},
},
});
定义组件
组件应接受 BaseComponentProps<TProps>:
interface BaseComponentProps<TProps> {
props: TProps; // Resolved props for this component
children?: Snippet; // Child elements (use {@render children()})
emit: (event: string) => void; // Fire a named event
bindings?: Record<string, string>; // Map of prop names to state paths (for $bindState)
loading?: boolean; // True while spec is streaming
}
<script lang="ts">
import type { BaseComponentProps } from "@json-render/svelte";
interface Props extends BaseComponentProps<{ label: string; variant?: string }> {}
let { props, emit }: Props = $props();
</script>
<button class={props.variant} onclick={() => emit("press")}>
{props.label}
</button>
<script lang="ts">
import type { Snippet } from "svelte";
import type { BaseComponentProps } from "@json-render/svelte";
interface Props extends BaseComponentProps<{ title: string }> {
children?: Snippet;
}
let { props, children }: Props = $props();
</script>
<div class="card">
<h2>{props.title}</h2>
{#if children}
{@render children()}
{/if}
</div>
创建注册表
import { defineRegistry } from "@json-render/svelte";
import { catalog } from "./catalog";
import Card from "./components/Card.svelte";
import Button from "./components/Button.svelte";
const { registry, handlers, executeAction } = defineRegistry(catalog, {
components: {
Card,
Button,
},
actions: {
submit: async (params, setState, state) => {
// handle action
},
},
});
规范结构(元素树)
Svelte schema 使用元素树格式:
{
"root": "card1",
"elements": {
"card1": {
"type": "Card",
"props": { "title": "Hello" },
"children": ["btn1"]
},
"btn1": {
"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 条件
Providers(通过 JsonUIProvider)
JsonUIProvider 组合所有上下文。各个上下文如下:
| 上下文 | 用途 |
|---|---|
StateContext | 在组件之间共享状态(JSON Pointer 路径) |
ActionContext | 处理通过事件系统分发的操作 |
VisibilityContext | 根据状态启用条件渲染 |
ValidationContext | 表单字段校验 |
事件系统
组件使用 emit 触发命名事件。元素的 on 字段将事件映射到操作绑定:
<script lang="ts">
import type { BaseComponentProps } from "@json-render/svelte";
interface Props extends BaseComponentProps<{ label: string }> {}
let { props, emit }: Props = $props();
</script>
<button onclick={() => emit("press")}>{props.label}</button>
{
"type": "Button",
"props": { "label": "Submit" },
"on": { "press": { "action": "submit" } }
}
内置操作
setState 操作会自动处理并更新状态模型:
{
"action": "setState",
"actionParams": { "statePath": "/activeTab", "value": "home" }
}
其他内置操作:pushState、removeState、push、pop。
动态属性和双向绑定
嵌套列表可以在外层 repeat 内将 repeat.statePath 设为 { "$item": "field" }。
组件接收属性前会解析以下表达式形式:
{"$state": "/state/key"}- 从状态中读取{"$bindState": "/form/email"}- 从状态中读取并回写{"$bindItem": "field"}- 为 repeat 项读取并回写{"$cond": <condition>, "$then": <value>, "$else": <value>}- 条件值
要在组件内使用可写绑定,请使用 getBoundProp:
<script lang="ts">
import { getBoundProp } from "@json-render/svelte";
import type { BaseComponentProps } from "@json-render/svelte";
interface Props extends BaseComponentProps<{ value?: string }> {}
let { props, bindings }: Props = $props();
let value = getBoundProp<string>(
() => props.value,
() => bindings?.value,
);
</script>
<input bind:value={value.current} />
上下文辅助函数
推荐使用的辅助函数:
getStateValue(path)- 返回{ current }(可读写)getBoundProp(() => value, () => bindingPath)- 返回{ current }(绑定时可读写)isVisible(condition)- 返回{ current }(布尔值)getAction(name)- 返回{ current }(已注册的处理程序)
高级上下文访问:
getStateContext()getActionContext()getVisibilityContext()getValidationContext()getOptionalValidationContext()getFieldValidation(ctx, path, config?)
流式 UI
使用 createUIStream 流式传输规范:
<script lang="ts">
import { createUIStream, Renderer } from "@json-render/svelte";
const stream = createUIStream({
api: "/api/generate-ui",
onComplete: (spec) => console.log("Done", spec),
});
async function generate() {
await stream.send("Create a login form");
}
</script>
<button onclick={generate} disabled={stream.isStreaming}>
{stream.isStreaming ? "Generating..." : "Generate UI"}
</button>
{#if stream.spec}
<Renderer spec={stream.spec} {registry} loading={stream.isStreaming} />
{/if}
使用 createChatUI 处理聊天和 UI 响应:
const chat = createChatUI({ api: "/api/chat-ui" });
await chat.send("Build a settings panel");
评论
支持 Markdown