React JSON Schema 表单实战 — 为什么它们会出现问题以及 SurveyJS 如何修复架构

发布: (2026年1月16日 GMT+8 06:57)
12 min read
原文: Dev.to

Source: Dev.to

根本原因

React JSON Schema 库将 schema 同时视为 结构 行为。它们依赖 schema 的变更(依赖关系、oneOf、动态重新生成的 schema),并且在很大程度上依赖 React 的重新渲染来应用逻辑。下面的大多数问题直接源于这种混淆。

SurveyJS 采用不同的方法:JSON 被视为 声明式模型,业务逻辑封装在表单引擎(survey‑core)中,React 仅用于渲染,而不用于编排。

RJSF 中的条件字段

在 RJSF 中,条件字段通常使用 dependenciesoneOf 或动态重新生成的 schema 实现:

const schema = {
  type: "object",
  properties: {
    hasCar: { type: "boolean" }
  },
  dependencies: {
    hasCar: {
      oneOf: [
        {
          properties: {
            hasCar: { const: true },
            carModel: { type: "string" }
          }
        },
        {
          properties: {
            hasCar: { const: false }
          }
        }
      ]
    }
  }
};

这仅在 schema 从未意外改变形状 时有效。在 React JSON Schema 表单中,schema 不仅是字段的描述——它 就是 表单本身。使用 dependenciesoneOfanyOf 会在运行时动态切换 schema 分支。从 React 的角度来看,字段被添加或移除,内部表单树的形状会改变,先前挂载的输入可能会卸载再重新挂载。

当满足以下条件时,这种方式是可管理的:

  • schema 是静态的。
  • 条件逻辑简单。
  • 没有外部数据影响 schema。

然而,实际产品表单往往需要:

  • 功能标记(feature flags)。
  • 后端驱动的配置。
  • 基于角色的可见性。
  • 逐步披露(progressive disclosure)。

一旦 schema 被动态修改(例如在 useEffect 中重新生成),React 会将其视为一个 全新的 表单定义,而不是对先前表单的延续。输入状态、默认值和验证可能会以出人意料的方式被重置。

渲染周期耦合

React JSON Schema 表单库还依赖 React 的渲染周期来协调行为:

  1. 字段变化更新 formData
  2. React 重新渲染。
  3. 可能会选择不同的 schema 分支并进行验证。

只有在更新严格按顺序进行时,这一链路才会正常工作。React 可能会批量更新或在合适的时机重新渲染组件,这会导致:

  • 字段闪烁。
  • 验证信息消失。
  • 输入失去焦点。

这些都不是 React 的 bug,而是将业务逻辑编码进渲染周期所导致的后果。

SurveyJS:静态 Schema + 运行时表达式

SurveyJS 通过修改 schema 来表达行为。相反,条件是由 survey‑core 在运行时求值的表达式:

{
  "name": "carModel",
  "type": "text",
  "visibleIf": "{hasCar} = true"
}

关键理念

  • Schema 保持 静态——表单结构只定义一次,并被视为持久的契约。
  • 字段从不被删除或重新创建;它们始终存在于模型中,为引擎提供稳定的引用点。
  • 可见性由引擎在运行时求值,使字段的出现具有确定性且不受 React 渲染时机的影响。

React 从不需要重新构建表单。它只渲染引擎报告为可见或激活的部分。状态变化触发规则求值,而不是结构性变更,从而消除了 schema 重新生成、深度相等检查或防御性 memo 化的需求。这种分离使 SurveyJS 能够在处理复杂的条件逻辑、大型 schema 和频繁的状态变化时保持稳健。

因此,SurveyJS 中的条件逻辑 可预测、可组合且可扩展,即使在非常大的表单中也是如此。

验证

在 React JSON Schema 表单中,验证通常委托给 Ajv,并映射回 React 状态:

// Example JSX (simplified)
<Form
  schema={schema}
  onChange={...}
  validate={...}
/>

团队经常会遇到以下问题:

  • 在模式更新后错误消失。
  • 异步验证与渲染竞争。
  • 需要自定义管道的复杂跨字段验证。

根本问题不在于 Ajv——而是验证绑定在 React 的渲染周期上。任何模式或状态的更改都可能导致错误树被重置。

SurveyJS 验证

SurveyJS 将验证视为 一等引擎关注点

{
  "name": "email",
  "type": "text",
  "isRequired": true,
  "validators": [
    { "type": "email" }
  ]
}
  • 验证独立于 React 运行。
  • 错误保存在调查模型中。
  • 跨字段或异步验证已内置。

将验证移出 React 后,就不需要对 setState 调用进行排序、手动防抖验证,或将异步结果与渲染进行调和。复杂规则保持可读,因为它们声明在影响的字段附近,而不是分散在组件逻辑中。

动态模式重新生成 – 常见陷阱

在 RJSF 中,更改 schema 通常意味着 替换 schema 对象,通常在 useEffect 中:

const [schema, setSchema] = useState(baseSchema);

useEffect(() => {
  if (formData.type === "advanced") {
    setSchema(advancedSchema);
  }
}, [formData]);

这种模式会导致 整个表单重新初始化、用户输入丢失以及验证失效。SurveyJS 通过保持 schema 静态并在引擎内部处理所有条件逻辑来避免这些问题。

摘要

方面React JSON Schema Forms (RJSF)SurveyJS Form Library
Schema 角色结构 以及 行为(已变更)纯声明式模型(静态)
Conditional logicdependenciesoneOf、模式再生成运行时表达式(visibleIfenableIf 等)
State handling与 React 渲染周期绑定;可能导致卸载/重新挂载引擎维护稳定的字段实例
ValidationAjv + React 状态;在模式更改时容易被重置引擎驱动,持久化于模型
Scalability在大型、动态表单中变得脆弱可预测、可组合,适用于大型模式

通过将 JSON 视为 静态契约 并将业务逻辑迁移到专用引擎,SurveyJS 将表单的 what(外观) 与 how(行为) 分离。这带来了更可靠、可维护且可扩展的表单——尤其在处理复杂、动态或大规模应用时。

为什么 SurveyJS 在大型、动态表单上优于 React JSON‑Schema Forms

React JSON‑Schema 库的问题

  • 模式变更 – 更新 JSON 模式(添加/删除字段、修改验证规则)会导致整个表单重新渲染。
  • 状态丢失 – 当模式改变时,用户输入、验证状态以及 UI 状态常常会被重置。
  • 性能下降 – 大型模式会导致首次渲染缓慢、深度对象比较开销大,以及不相关字段的无谓重新渲染。

开发者通常会通过记忆化、深度合并或模式差分来缓解这些问题,但这些变通方案会增加复杂度,且仍未根本解决问题。

SurveyJS 的做法

  1. 静态模式 – JSON 模式在运行时永不改变。
  2. 运行时表达式 – 条件可见性、必填逻辑和验证都以公式的形式由 SurveyJS 引擎求值。
  3. 关注点分离
    • survey‑core – 与平台无关的模型,保存表单定义、状态和业务逻辑。
    • survey‑react‑ui – 轻量的 React 包装层,只负责渲染引擎标记为“活跃”的内容。

因为 React 只负责渲染,它无需评估条件、管理验证状态或调和模式变更。这带来:

  • 可预测的行为
  • 在动态变化时保持用户输入和验证状态
  • 对大型表单的显著性能提升

性能优势

问题React JSON‑Schema 库SurveyJS
大型模式的首次渲染缓慢(完整树渲染)快速(增量渲染)
更新单个字段可能触发整个表单重新渲染仅重新评估受影响的字段
每次渲染的深度比较开销大被避免 – 引擎自行处理差分
懒加载页面/问题很少支持内置,仅渲染可见项

最小示例(在 React 中使用 SurveyJS)

import { Survey } from "survey-react-ui";
import { Model } from "survey-core";

const surveyJson = {
  /* … your survey definition … */
};

export default function SurveyComponent() {
  const survey = new Model(surveyJson);   // 引擎处理逻辑
  return <Survey model={survey} />;       // React 只负责渲染 UI
}

幕后工作原理 – SurveyJS 引擎实时求值可见性条件、动态规则和验证,而无需对整个模式进行差分。

你会使用的包

  • survey‑core – 与平台无关的调查模型(业务逻辑)。
  • survey‑react‑ui – React 专用渲染器。
  • survey‑creator‑core – 拖拽式 Survey Creator 的模型。
  • survey‑creator‑react – Survey Creator 的 React UI。

了解更多架构信息:SurveyJS Architecture (link)。

何时选择 SurveyJS

  • 表单 长期存在 并会随时间演进。
  • 条件逻辑(显示/隐藏、启用/禁用、动态验证)是核心需求。
  • 需要 大规模高性能(数百字段、多页调查)。
  • 验证 复杂(自定义表达式、跨字段规则)。
  • 可变的 JSON 模式已成为 维护负担

React JSON‑Schema 表单仍然适用于简单、静态的表单,但在上述场景下,SurveyJS 提供了更清晰、更快且更易维护的解决方案。

资源

大多数被归咎于“React JSON‑Schema 表单”的问题并非 bug,而是固有的架构限制。SurveyJS 将繁重的工作从 React 中抽离,使产品团队能够专注于功能,而不是与渲染周期作斗争。

Back to Blog

相关文章

阅读更多 »