Skip to content

Texaryn:一份 JSON Schema,在 React、Vue 或 Web Component 中渲染 ​

译注:本文编译自 dev.to 上 Hamza Hamidi 的文章,原文发布于 2026 年 9 月 24 日。原文链接见文末。Texaryn 目前仍处于 1.0 之前的阶段。

Texaryn 是一个面向 JSON Schema 表单的无头(headless)运行时。它的结构分为三层:schema 适配器负责读取 Draft 7、2019-09 或 2020-12;与框架无关的核心负责状态、校验和提交;React、Vue 或 Web Components 绑定负责渲染结果。

如果你用过 react-jsonschema-form(RJSF),这里有几处不同:数组行的身份标识(row identity)由运行时持有,跟随该行的数据或你指定的 itemKey;每个字段可以自行选择在 blur、change 还是 submit 时校验;同一套运行时可以渲染在 React、Vue 或 Web Component 之下。项目目前尚未发布 1.0。

为什么要做这个 ​

作者自 2018 年起维护 Angular JSON Schema 表单库 AJSF,它延续了 dschnelldavis/angular2-json-schema-form,首个 commit 于 2018 年 7 月把代码带到 Angular 6。AJSF 的主版本号跟随其支持的 Angular 主版本,22.2.1 对应 Angular 22。

该项目目前有 359 stars 和 177 forks,@ajsf/core 在 2026 年 8 月 23 日至 9 月 21 日期间有 17,630 次 npm 下载。

AJSF 已经把 schema 逻辑与设计系统(Material、Bootstrap、PrimeNG)分离,但整体仍运行在 Angular 之内。Texaryn 由此生长出来,也源于对「AI 构建界面需要什么」的探索:把界面描述为结构化数据,让运行时持有状态与校验,渲染则留给每个框架的薄层。

在 RJSF 中,Form 在 React 内部完成 schema 处理、状态管理和渲染。作者希望有三个各自独立的角色:理解 JSON Schema 的适配器、持有表单状态的运行时,以及渲染「框架中立表单描述」的绑定层。

目标用户是用 JSON Schema 构建表单密集型 React 应用的人:配置界面、多步引导、管理后台、数据录入工具。他们需要逐字段的 dirty 与 touched 状态、逐字段的校验时机,以及无需 fork 主题就能自定义组件——今天用 React,但不被 React 锁死。Vue 被刻意选作第二个绑定:setup 只运行一次,响应式按 ref 追踪,而 React 每次渲染都会重跑组件,这能检验绑定契约是否悄悄假定了 React 的模型。

工作原理 ​

JSON Schema
    ↓  adapter (@texaryn/schema-json)
SchemaProjection
    ↓  compiler (@texaryn/core)
UIDocument
    ↓  runtime (@texaryn/core)
FormRuntime state
    ↓  binding (@texaryn/react, @texaryn/vue, @texaryn/web-components)
Widgets (a default set per binding; Bootstrap 5 and Material UI for React)

createJsonSchemaAdapter 返回一个实现 SchemaEvaluationPort 的对象,运行时通过该接口投影和校验数据。投影(projection)是当前数据对应的字段、约束和生效分支,因此会随用户编辑而重新计算。

编译器把投影转成 UIDocument:一棵带版本、由类型化节点组成的树,描述表单包含什么,而不是 React、Vue 或 DOM 如何绘制它。绑定接收的是这份文档而非 schema,因此条件、组合与依赖只在一处——适配器——求值,任何绑定都不需要重新实现它们。

FormRuntime 持有值、校验、dirty 与 touched 状态、可见性、禁用状态、提交以及数组身份。绑定订阅它,而不是各自保留一份副本。核心没有任何依赖,UI 定义就是数据:不生成 JavaScript,也不使用 eval()。

渲染侧,绑定(@texaryn/react、@texaryn/vue、@texaryn/web-components)把运行时接到某个框架上。注册表(registry)把每个 UI 节点映射到组件,而 @texaryn/react-bootstrap、@texaryn/react-mui 这样的 widget set 就是 React 现成的注册表。

仓库会检验「中立性」这一主张:目录中的每个示例都会跑过每一种绑定和 widget set,当某个已文档化的特性没有示例时构建会失败。playground 用 React Default、React Bootstrap 5、React Material UI、Vue Default 和 Web Components Default 展示同一批示例。

示例 schema ​

三个示例都渲染这份 schema.ts:

typescript
export const schema = {
  $schema: 'https://json-schema.org/draft/2020-12/schema',
  type: 'object',
  title: 'Profile',
  properties: {
    name: { type: 'string', title: 'Name', minLength: 1 },
    age: { type: 'integer', title: 'Age', minimum: 18 },
  },
  required: ['name'],
}

required 只检查键是否存在,而 '' 也算一个值,所以真正拒绝空名字的是 minLength: 1。

在 React 中 ​

shell
pnpm add @texaryn/core @texaryn/schema-json @texaryn/react react react-dom

@texaryn/react 需要 React 18 或更高版本。这些包只提供 ES module,示例在模块顶层 await 适配器,因此需要支持该特性的构建目标,例如 es2022。

tsx
import { createRoot } from 'react-dom/client'
import { createJsonSchemaAdapter } from '@texaryn/schema-json'
import {
  ErrorSummary,
  FormProvider,
  FormRoot,
  createDefaultRegistry,
  useForm,
} from '@texaryn/react'
import { schema } from './schema'

const adapter = await createJsonSchemaAdapter(schema)
const registry = createDefaultRegistry()

function App() {
  const form = useForm(adapter, {
    initialData: { name: '', age: 18 },
    hints: {
      '/name': { placeholder: 'Ada Lovelace', validationTrigger: 'blur' },
    },
    onSubmit: async (data) => console.log(data),
  })

  return (
    <FormProvider value=&#123;form.runtime&#125;>
      <ErrorSummary />
      <FormRoot registry=&#123;registry&#125; />
      <button
        type="button"
        disabled={
          form.submission.status === 'validating' ||
          form.submission.status === 'submitting'
        }
        onClick={() => form.dispatch({ type: 'Submit' })}
      >
        Submit
      </button>
    </FormProvider>
  )
}

createRoot(document.getElementById('root')!).render(<App />)

useForm 创建运行时并订阅其 store,FormProvider 让它对 ErrorSummary 和 FormRoot 可用。FormRoot 只渲染字段,外面没有 <form>,所以按钮自己派发 Submit 命令,按 Enter 不会提交。名字为空时 onSubmit 不会被调用,ErrorSummary 会在「There is a problem」标题下获得焦点,并列出 Name: Value #/nameshould have a minimum length of1, but got 0.。

这条消息来自校验器,目前还无法改写。默认 widget 会设置 aria-invalid,并通过 getInputProps 和 getErrorProps 关联 aria-describedby,自定义 widget 也可以调用它们。

hints 与 initialData 放在同一份 options 中,以 JSON Pointer 为键。字段 hint 可设置 widget、order、placeholder、helpText 或 validationTrigger('blur'、默认防抖 300 ms 的 'change',或 'submit'),数组 hint 还可加 itemKey 和 canReorder。没有 validationTrigger 的字段只在提交时校验,因此在下面的 Vue 和 Web Component 示例中,错误会一直保留到下一次提交。

提交是一个状态机:idle、validating、submitting、submitted。提交会校验数据的一份不可变快照,validating 期间的编辑会取消本次尝试。

在 Vue 中 ​

shell
pnpm add @texaryn/core @texaryn/schema-json @texaryn/vue vue

@texaryn/vue 需要 Vue 3.5 或更高版本。main.ts:

typescript
import { createApp } from 'vue'
import { createJsonSchemaAdapter } from '@texaryn/schema-json'
import App from './App.vue'
import { schema } from './schema'

const adapter = await createJsonSchemaAdapter(schema)

createApp(App, { adapter }).mount('#app')

App.vue:

vue
<script setup lang="ts">
import type { SchemaEvaluationPort } from '@texaryn/core'
import {
  ErrorSummary,
  FormRoot,
  createDefaultRegistry,
  provideFormRuntime,
  useForm,
} from '@texaryn/vue'

const props = defineProps<{ adapter: SchemaEvaluationPort }>()

const form = useForm(props.adapter, {
  initialData: { name: '', age: 18 },
  onSubmit: async (data) => console.log(data),
})
provideFormRuntime(form.runtime)

const { submission, dispatch } = form
const registry = createDefaultRegistry()
</script>

<template>
  <ErrorSummary />
  <FormRoot :registry="registry" />
  <button
    type="button"
    :disabled="submission.status === 'validating' || submission.status === 'submitting'"
    @click="dispatch({ type: 'Submit' })"
  >
    Submit
  </button>
</template>

适配器在 main.ts 中创建并作为 prop 传入,因此 setup 保持同步,也不需要 <Suspense>。provideFormRuntime 取代了 FormProvider,这也是 ErrorSummary 和 FormRoot 必须放在该组件模板中的原因:它们必须是提供运行时的组件的后代。

@texaryn/vue 提供的是渲染函数,因此单文件组件是你的选择而非硬性要求。

作为 Web Component ​

shell
pnpm add @texaryn/core @texaryn/schema-json @texaryn/web-components
typescript
import { createJsonSchemaAdapter } from '@texaryn/schema-json'
import { createDefaultRegistry, defineTexarynForm } from '@texary

(原文此处代码块被截断。)

原文链接 ​

https://dev.to/hamzahamidi/texaryn-one-json-schema-rendered-in-react-vue-or-a-web-component-85c