mcpField
J
json-render-react

json-render-react

github.com/vercel-labs開発18.8k391 回インストール4 日前に更新
リポジトリ

json-render-react について

@json-render/reactは、データ駆動型UIを構築する開発者向けに、JSON仕様をReactコンポーネントツリーとして描画します。状態バインディング、条件表示、イベント処理、フォーム検証を備えています。

主な機能

  1. 01仕様をReactコンポーネントツリーに描画
  2. 02型安全なコンポーネントレジストリ
  3. 03状態の読み取りと双方向バインディング
  4. 04条件付き描画と状態ウォッチャー
  5. 05イベント処理とフォーム検証

活用シーン

  1. 01JSON仕様から動的にUIを構築
  2. 02外部状態に連動するフォームの作成
  3. 03状態に応じた表示制御とアクション実行

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/vercel-labs/json-render --skill json-render-react

インストール後、Claude Code で/json-render-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() }),
      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>
    ),
  },
});

仕様の構造(要素ツリー)

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 条件には { "$and": [cond1, cond2] }、OR 条件には { "$or": [cond1, cond2] } を使用します。ヘルパー:visibility.when("/path")、visibility.unless("/path")、visibility.eq("/path", val)、visibility.and(cond1, cond2)、visibility.or(cond1, cond2)。

プロバイダー

プロバイダー目的
StateProviderコンポーネント間で状態を共有します(JSON Pointer パス)。制御モード用の store プロパティを任意で指定できます。
ActionProviderイベントシステムを介してディスパッチされるアクションを処理します
VisibilityProvider状態に基づく条件付きレンダリングを有効にします
ValidationProviderフォームフィールドの検証を行います
外部ストア(制御モード)

外部状態管理(Redux、Zustand、XState など)を使用するには、StateProvider(または JSONUIProvider / createRenderer)に StateStore を渡します。

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 は無視されます。

動的なプロパティ式

任意のプロパティ値にデータ駆動型の式を指定できます。コンポーネントにプロパティが渡される前に、レンダラーによって評価されます。

  • { "$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" } を指定します。

コンポーネントには、解決済みのプロパティが渡されます。双方向バインドされたプロパティには、レンダラーが提供する bindings マップと useBoundProp フックを使用します。

JSONUIProvider または createRenderer の functions プロパティを介して、$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 によって自動的に処理されます。カタログの actions に宣言しなくても、AI プロンプトに挿入されます。

{ "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)は、変更対象のパスを指定します。コンポーネントのプロパティで双方向バインディングを行う場合は、statePath ではなく、値のプロパティに { "$bindState": "/path" } を指定します。

useBoundProp

双方向バインディングが必要なフォームコンポーネントでは、プロパティに { "$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 は解決済みのプロパティで、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 を返します)
useUIStreamAPI エンドポイントから仕様をストリーミングします
createStateStoreフレームワークに依存しないインメモリの StateStore を作成します
StateStore外部の状態管理を接続するためのインターフェース
BaseComponentProps再利用可能なコンポーネントライブラリ向けの、カタログに依存しない基本型
EventHandleイベントハンドル型(emit、shouldPreventDefault、bound)
ComponentContext型付きコンポーネントコンテキスト(カタログ対応)

コメント