ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

@fumari/stf:fumadocs 中“Schema to Form“状态数据引擎的设计与演进

@fumari/stf:fumadocs 中“Schema to Form“状态数据引擎的设计与演进 fumari/stffumadocs 中Schema to Form状态数据引擎的设计与演进【免费下载链接】fumadocsThe beautiful flexible React.js docs framework.项目地址: https://gitcode.com/GitHub_Trending/fu/fumadocsfumari/stf 是 fumadocs 文档框架中面向 Schema 驱动的表单状态管理包Schema to Form它把 JSON Schema 到表单 UI 之间最棘手的字段状态同步问题收敛为一个可响应的数据引擎与一组 React Hooks。本文以 packages/stf/CHANGELOG.md 的版本脉络为骨架结合 packages/stf 源码、测试与上游使用方代码讲解其数据模型、核心 API、版本演进与实际落地方式帮助你理解并复用这套状态管理思路。一、从变更日志看包的定位与演进packages/stf/CHANGELOG.md 记录了fumari/stf从0.0.1到1.1.0的完整演进虽然条目简短但每条都对应着源码中真实存在的能力版本变更内容源码佐证0.0.1beta 发布首个可用版本0.0.2升级 tsdown 构建工具packages/stf/tsdown.config.ts 使用 tsdown 构建0.0.3修复 change detector变更检测器对应>export type FieldKey (string | number)[];它用字符串与数字混排的数组描述数据树中的任意路径[user, name]表示data.user.name[todos, 0, title]表示数组todos中第 0 项的title。数组下标用number对象属性用string一套寻址模型同时覆盖对象与数组。围绕 FieldKeypackages/stf/src/lib/utils.ts 提供了一组纯函数export function objectGet(obj: unknown, key: (string | number)[]): unknown | undefined export function objectSet(obj: unknown, field: FieldKey, value: unknown): unknown export function stringifyFieldKey(fieldKey: FieldKey): string export function fieldKeyStartsWith(a: string, b: string): boolean export function arrayStartsWith(a: FieldKey, b: FieldKey): boolean export function isPlainObject(value: unknown): value is Recordstring, unknown其中stringifyFieldKey的实现值得注意——字符串键加_前缀、数字键加n前缀后以.连接export function stringifyFieldKey(fieldKey: FieldKey) { return fieldKey.map((v) (typeof v string ? _${v} : n${v})).join(.); }这种编码方式避免了[a, 0]与[a.0]在字符串比较时产生歧义fieldKeyStartsWith也因此能用精确的a b || a.startsWith(b .)判断某个字段是否位于另一字段的子树下这是事件派发时精确命中监听器的基石。这些工具函数通过 package.json 的./lib/utils子路径对外导出对应 CHANGELOG 1.0.4 的暴露更多工具。三、DataEngine可监听的字段状态引擎packages/stf/src/lib/data-engine.ts 中的DataEngine类是 stf 的运行时核心它维护一棵普通对象数据树并提供四个修改原语init(field, defaultValue, ctx)初始化字段字段已存在时直接返回现值不存在时才写入默认值支持沿路径自动创建中间对象或数组中间键为数字时自动创建数组update(key, value, ctx)就地更新字段值依赖objectSet父对象不存在时抛出错误并返回falsedelete(key, ctx)删除字段数组元素删除使用splice并区分删除末尾元素与删除中间元素两种语义get(key)读取字段值getData()返回整棵数据树。所有变更都会经过内部的ListenerManager派发事件。监听器接口DataEngineListener支持三个回调export interface DataEngineListener { field?: FieldKey; // 只监听指定字段缺省则监听全部 onUpdate?: (key: FieldKey, ctx: OnUpdateContext) void; onInit?: (key: FieldKey, ctx: OnInitContext) void; // init(field) 时触发 onDelete?: (key: FieldKey, ctx: OnDeleteContext) void; }onUpdate的上下文里有一个关键字段swallow吞掉export interface OnUpdateContext { /** 当变更不影响子字段值时该更新会被吞掉 */ swallow: boolean; custom?: Recordstring, unknown; }它的语义在ListenerManager.onUpdate中体现若swallow true只有精确匹配该字段的监听器收到通知反之则所有以该字段为前缀的监听器都会收到如删除数组中间元素后后续元素下标全部变化。测试 packages/stf/test/index.test.ts 验证了这一整套流程——初始化嵌套字段、删除数组中间项后日志依次输出init hello、init hello,world、update trueswallow 为 true 的父级初始化、update hello,world false删除导致子级字段变化的非 swallow 更新。namespace隔离的子引擎1.0.0引入的 namespace 能力data-engine.ts允许在主引擎下创建互相隔离的DataEngine子实例namespace(namespace: string, initialValue?: DefaultValueNonNullableobject, config?: NamespaceConfig)同一 namespace 多次调用返回同一个子引擎实例config.reset回调会在主引擎reset()时被调用用于清理/重置子引擎状态——这正是 1.0.1支持清理 namespace 信息的实现落点reset(data: NonNullableobject) { this.update([], data); for (const { engine, reset } of this.namespaces.values()) reset?.({ engine }); }主数据与 namespace 数据彻底隔离适合把表单主体与临时 UI 状态分开管理。四、React Hooks把引擎接入组件树packages/stf/src/lib/stf.tsx 在引擎之上提供了一组use client的 React Hooks全部导出自 packages/stf/src/index.ts。4.1 顶层StfProvider 与 useStfuseStf({ defaultValues })创建并持有唯一一个DataEngine内部用useMemo保证实例稳定通过StfProvider注入组件树。注意 JSDoc 提示传入的defaultValues对象会被就地修改如需保留原始对象请先structuredClone()。4.2 useFieldValue字段级的受控绑定useFieldValue(key, options)返回[value, setValue]是表单控件绑定的基础const [value, setValue] useFieldValue([user, name], { defaultValue: fuma, // 首次渲染时自动 init compute: (v) normalize(v), // 从原始值计算展示值 isChanged: (a, b) a ! b, // 自定义变更判定 });它内部用useListener订阅onInit/onUpdate/onDelete字段被删除时以compute(undefined)的结果回填换引擎如 namespace 切换时也会同步重算。4.3 useArray动态列表const { items, insertItem, removeItem } useArray([todos], { defaultValue: [], });items是{ field: FieldKey; index: number }[]每项携带可继续下钻的 FieldKeyinsertItem在末尾initremoveItem走engine.delete。其变更检测通过isChanged(prev, next) prev.length ! next.length实现只关心数组长度。4.4 useObjectSchema 驱动对象的三种属性来源useObject(field, options)是 JSON Schema 到表单字段映射的关键它支持三种属性固定属性fixedSchema 中显式声明的properties模式属性patternpatternProperties用正则匹配动态键如^x-兜底属性fallbackadditionalProperties匹配剩余的未知键。同时提供lazy选项为true时未定义的固定属性先不渲染避免大 Schema 一次性生成上百个输入框配合onAppend(name, value)由用户按需添加。compute用Object.keys加deepEqual做变更判定保证对象键的增删能驱动重渲染。4.5 useListener 与 useNamespaceuseListener(listener)把监听器生命周期绑定到useEffect卸载时自动unlisten并通过listenerRef保持回调最新useNamespace({ namespace, initial })在渲染期创建/获取子引擎并注入reset 时恢复 initial 值的配置。五、实际落地openapi playground 的表单生成stf 并不是孤立存在的工具它在 fumadocs 的 API 文档体系中承担着根据 OpenAPI Schema 生成可交互请求表单的职责。以 packages/api-docs/src/components/playground/inputs.tsx 为例const { properties, onAppend, onDelete, _objectKeys } useObject(fieldName, { lazy: isLazy, // 属性超过 100 个时惰性渲染 defaultValue: () generateDefault(field) as object, properties: field.properties ?? {}, fallback: additionalProperties, patternProperties, });这里lazy的默认值schemaPropKeys.length 100来自源码中x-playground-lazy扩展属性patternProperties/additionalProperties直接对应 Schema 关键字。同样的模式也出现在 packages/openapi/src/playground、packages/asyncapi/src/ui 与 packages/story/src/client 中——一个fumari/stf的实例可同时服务 API playground 与组件 Story 的参数编辑佐证了Schema to Form这一包定位。六、工程细节与使用建议构建tsdown 打包为 ESMtarget: es2023入口为./src/index.{ts,tsx}与./src/lib/utils.ts见 tsdown.config.ts依赖peerDependencies 为react/react-dom^19.2.0运行时零第三方依赖——状态逻辑与 UI 完全解耦这也是 1.1.0 能在 Base UI / Radix UI 之间平滑切换的前提调试engine.init()覆盖已存在的非对象值时会在控制台打印警告the original value of field ... is overridden帮助及早发现 Schema 默认值与用户输入冲突。适用场景小结当你需要由一段 Schema 描述驱动一棵可响应、可监听的表单状态树时典型如 OpenAPI/AsyncAPI playground、动态属性编辑器可以直接复用DataEngine useFieldValue/useArray/useObject这套组合多个隔离状态域用namespace拆分UI 层则完全交给自己的组件库。使用限制本包通过fumari/stf发布仓库内使用方通过 workspace 依赖引用FieldKey 寻址与swallow派发语义对性能敏感的超深层级数据结构仍需自行验证工具函数注释明确说明deepEqual不处理递归对象。版本演进上若你正在维护基于attachedData()旧 API 的代码需注意该 API 已在 1.0.0 移除请改用 namespace 机制。【免费下载链接】fumadocsThe beautiful flexible React.js docs framework.项目地址: https://gitcode.com/GitHub_Trending/fu/fumadocs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表