ARTICLE DETAIL

资讯详情

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

Sanity Schema 中的 Member Types(成员类型)深入解析:Object 字段、Array 元素与 Reference 目标的底层实现

Sanity Schema 中的 Member Types(成员类型)深入解析:Object 字段、Array 元素与 Reference 目标的底层实现 Sanity Schema 中的 Member Types成员类型深入解析Object 字段、Array 元素与 Reference 目标的底层实现【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanitymember-types.mdpackages/sanity/schema/member-types.md与sanity/schema包的 READMEpackages/sanity/schema/README.md共同定义了 Sanity 内容建模体系中的成员类型Member type概念它指那些不作为独立 Schema 类型注册而是作为某个宿主类型内部成员存在的类型。本文将从概念定义出发结合sanity/schema的Schema.ts编译注册器与object.ts、array.ts、reference.ts、blocks/block.ts等核心实现讲清成员类型的三种形态、典型使用场景及其在编译期的缓存与懒加载机制帮助你准确理解 Sanity Schema 的类型解析行为。什么是 Member Type按照 packages/sanity/schema/README.md 中的 Terminology 定义Schema一组类型的集合a collection of typesType一种数据结构的规格说明可以通过 schema 查找schema lookup获取Member type被某个 schema 类型包含的类型。例如一个数组array可以通过定义成员类型来指定允许的元素类型一个引用reference可以是针对一组其他类型的引用。成员类型不会加入 schema也不可通过 schema lookup 获取它只作为宿主类型的一个属性存在。member-types.md用一句话点明了本质A member type is a type that is not a part of the schema, but rather exists as a member of another type. For example, each fields of an object has fields which are just specialization of schema types.即成员类型是 schema 类型在特定上下文中的特化specialization。典型的例子就是 object 的fields中的每一个字段——每个字段本质上都是一个 schema 类型string、number、object……的成员化特化。成员类型与顶层类型的三大区别从 README 的 Terminology 和 Schema.ts 的实现可以看出成员类型与顶层类型存在三个关键差异维度顶层类型Schema Type成员类型Member Type注册位置加入_registry是 schema 的一等公民仅作为宿主类型的属性存在不进入 registry查找方式可通过schema.get(name)/schema.has(name)获取不可通过 schema lookup 获取生命周期由schemaDef.types直接声明由宿主的fields、of、to等属性声明编译时实例化在 Schema.ts 中顶层类型通过schemaDef.types.reduce建立defsByName索引而成员类型则通过extendMember在宿主类型扩展extend的过程中被创建从不进入该索引。三种成员类型的编译形态sanity/schema的编译注册器compileRegistry位于 Schema.ts提供了三个创建成员类型的入口分别对应三种不同的成员形态1.extendMember—— 通用成员类型extendMember是基础入口Schema.tsfunction extendMember(memberDef: any) { ensure(memberDef.type) return registry[memberDef.type].extend(memberDef, extendHelper).get() }它的逻辑是先通过ensure确认成员定义所依赖的基类型已注册然后委托给对应基类型string、number、object……的extend方法进行特化最后调用.get()取出解析结果。reference的to数组中的目标类型就是通过这种方式创建的// packages/sanity/schema/src/legacy/types/reference.ts#L73-L75 lazyGetter(parsed, to, () { return arrify(subTypeDef.to).map((toType: any) createMemberType(toType)) })2.cachedField—— 带名称的字段成员cachedField用于创建有名的字段成员Schema.tscachedField(fieldDef: any) { let field fieldCache.get(fieldDef) if (!field) { const {name, ...type} fieldDef field { name, type: extendMember(type), } fieldCache.set(fieldDef, field) } return field }它将字段定义拆分为name与剩余的类型定义再用extendMember实例化类型部分。reference的内部字段如_ref、_weak就是通过它创建的reference.tslazyGetter(parsed, fields, () { return REFERENCE_FIELDS.map((fieldDef) createMemberType.cachedField(fieldDef)) })crossDatasetReference与globalDocumentReference的字段也采用同样的cachedField方式见 crossDatasetReference.ts、globalDocumentReference.ts。3.cachedObjectField—— object 字段含 fieldset / groupcachedObjectField是 object 类型字段的专用入口Schema.tscachedObjectField(fieldDef: any) { let field objectFieldCache.get(fieldDef) if (!field) { const {name, fieldset, group, ...rest} fieldDef field { name, group, fieldset, } lazyGetter(field, type, () { return extendMember({ ...rest, title: fieldDef.title || startCase(name), }) }) objectFieldCache.set(fieldDef, field) } return field }它与cachedField的关键差异是会额外保留fieldset与group元数据用于字段分组和 fieldset 展示并且类型的实例化被推迟到field.type首次被访问时才执行lazyGetter。object.ts 中 object 的fields正是通过它生成的fields: subTypeDef.fields.map((fieldDef: any) createMemberType.cachedObjectField(fieldDef), ),成员类型的两大典型承载属性of与to除了 object 的fields成员类型还大量出现在数组的of与引用的to中。数组的of在 array.ts 中数组允许的成员类型由of数组声明且通过cached带缓存版本的extendMember实例化lazyGetter(parsed, of, () { return subTypeDef.of.map((ofTypeDef: any) createMemberType.cached(ofTypeDef)) })注意这里的createMemberType.cached与createMemberType的区别cached会先用memberCache做去重缓存Schema.ts保证同一个成员定义在多次引用时只被实例化一次从而在大型 Schema如 blocks 中大量复用的内联对象下节省编译开销。引用的toreferencereference.ts、crossDatasetReference、globalDocumentReference都通过to声明可被引用的目标类型集合目标成员同样经由createMemberType特化。此外reference在扩展时若缺少to会直接抛出错误if (!subTypeDef.to) { throw new Error( Missing to field in reference definition. Check the type ${subTypeDef.name}, ) }Block 类型成员类型嵌套的极致案例Portable Text 的block类型blocks/block.ts是理解成员类型嵌套的最佳样例。它在扩展时通过extendMember.cachedField实例化自身的五个内置字段lazyGetter(parsed, fields, () { return fields.map((fieldDef) extendMember.cachedField(fieldDef)) })其中childrenField、markDefsField等又是嵌套的成员类型markDefsField是一个array类型其of来自marks?.annotations || DEFAULT_ANNOTATIONSblock.ts。也就是说block 的字段是成员类型而字段本身又是数组成员类型的宿主——成员类型可以任意层嵌套形成成员中的成员。源码注释还特别提醒这些字段的顺序若调整必须同步更新sanity/types中的BlockSchemaType。组合优于继承成员类型的设计哲学README 的 Constraints 部分强调了成员类型存在的一个重要前提——Sanity Schema 不支持类型继承开发者应优先使用组合composition而非继承层级const PERSON { type: object, name: person, fields: [ {name: firstName, type: string}, { name: address, type: object, fields: [{name: street, type: string}], }, ], }若试图把user建模为person的子类型type: person再追加字段会立即遇到难以回答的问题字段如何合并PERSON的字段放在USER的字段之前还是之后同名字段是否覆盖对象字段是整体覆盖还是局部保留README 明确给出结论It quite quickly becomes messy很快就会变得一团糟。正确的做法是提取公共字段定义并在不同类型间复用const FIRST_NAME_FIELD {name: firstName, type: string} const ADDRESS_FIELD { name: address, type: object, fields: [ {name: zip, type: string}, {name: street, type: string}, {name: city, type: string}, ], } const PERSON { type: object, name: person, fields: [FIRST_NAME_FIELD, ADDRESS_FIELD], } const USER { type: object, name: person, fields: [FIRST_NAME_FIELD, {name: username, type: string}, ADDRESS_FIELD], }甚至可以进一步把address的各个字段拆出来按需自由组合。这种字段即成员、成员可复用的模型正是成员类型机制在 schema 定义层面提供的灵活性的体现。成员类型的编译期行为小结综合 Schema.ts 的实现成员类型在编译期呈现以下行为不进入 registry成员类型不会出现在schema.getTypeNames()/schema.getLocalTypeNames()中仅作为宿主类型的fields、of、to等属性存在按需懒加载数组的of、引用的to、block 的fields等均通过lazyGetter延迟实例化字段类型cachedObjectField中的field.type也仅在首次访问时才创建避免无效编译开销缓存去重memberCache/fieldCache/objectFieldCache三个 Map 分别缓存三类成员确保同一成员定义在 Schema 内只实例化一次基于基类型特化每个成员类型都经由对应基类型的extend(memberDef, extendHelper)特化而来所以成员类型天然继承了基类型的全部行为又叠加了自身的覆盖项——这是组合优于继承在实现层的落地。理解成员类型是深入掌握 Sanity Schema 编译流程Schema.compile→compileRegistry→extendMember、排查为什么这个类型查不到类问题以及正确设计可复用字段抽象的前提。相关测试可在 extractSchema.test.ts 中查看fields/of/to成员声明的解析断言作为进一步验证的参考。【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表