ARTICLE DETAIL

资讯详情

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

normalizr API 完全指南:normalize / denormalize 与五大 Schema 的源码级解析

normalizr API 完全指南:normalize / denormalize 与五大 Schema 的源码级解析 normalizr API 完全指南normalize / denormalize 与五大 Schema 的源码级解析【免费下载链接】normalizrNormalizes nested JSON according to a schema项目地址: https://gitcode.com/gh_mirrors/no/normalizr本篇技术指南以 normalizr 的官方 API 文档docs/api.md为骨架系统讲解normalize、denormalize两个核心函数与Array、Entity、Object、Union、Values五种 Schema 的全部参数、选项、实例方法与输出格式并结合本仓库源码src/index.js 与 src/schemas/揭示其底层实现原理。读完本文你将能针对任意嵌套 JSON 设计 Schema、精准预测归一化输出并掌握多态归一化、循环引用、ID 生成策略等进阶用法直接应用于 Redux/React 状态管理场景。一、normalize按 Schema 将嵌套 JSON 拍平为实体字典normalize(data, schema)是 normalizr 的入口函数负责把深度嵌套的 JSON或普通 JS 对象按照给定的 schema 定义转换为扁平化的实体字典 引用树结构。1.1 函数签名与参数data必填。需要归一化的输入 JSON 或普通 JS 对象。schema必填。一个 schema 定义Entity、Array、Object、Union、Values或它们的嵌套组合也支持字面量简写语法。1.2 基本用法import { normalize, schema } from normalizr; const myData { users: [{ id: 1 }, { id: 2 }] }; const user new schema.Entity(users); const mySchema { users: [user] }; const normalizedData normalize(myData, mySchema);1.3 输出结构{ result: { users: [ 1, 2 ] }, entities: { users: { 1: { id: 1 }, 2: { id: 2 } } } }输出的两个关键部分result归一化后的数据骨架所有被识别为实体的对象都被替换为其 ID对集合类 schema 则是 ID 数组 / ID 映射entities按实体类型的key分组的字典entities.users[1]可以直接通过 ID 查询。从源码看normalize的实现非常简洁src/index.js它先校验输入必须是对象否则抛出Unexpected input given to normalize...错误随后初始化空的entities字典与visitedEntities用于防止循环引用导致死循环再通过递归的visit函数遍历整棵树。visit的核心逻辑src/index.js是如果当前值不是对象typeof value ! object直接原样返回如果 schema 本身没有normalize方法即传入的是字面量对象{ ... }或数组[ ... ]则按数组/对象分别委托给ArrayUtils.normalize或ObjectUtils.normalize这正是简写语法能工作的底层原因否则调用 schema 实例的normalize方法继续递归。addEntitiessrc/index.js则负责把处理完的实体写入entities字典若同一key下已存在相同 ID 的实体会调用schema.merge即mergeStrategy合并。二、denormalize归一化数据的逆操作denormalize(input, schema, entities)根据 schema 与实体字典把归一化结果还原为嵌套对象是normalize的逆过程。2.1 函数签名与参数input必填。需要反归一化的输入通常是normalize输出中result键的值。schema必填。与当初用于生成input的 schema 定义一致。entities必填。以实体 schema 名key为键的对象键值为各实体 ID 到实体数据的映射也支持 Immutable 数据结构。2.2 基本用法import { denormalize, schema } from normalizr; const user new schema.Entity(users); const mySchema { users: [user] }; const entities { users: { 1: { id: 1 }, 2: { id: 2 } } }; const denormalizedData denormalize({ users: [1, 2] }, mySchema, entities);2.3 输出{ users: [{ id: 1 }, { id: 2 }]; }2.4 重要注意事项性能警告不要过早地对数据进行反归一化。把数据提前还原成庞大、深嵌套的对象在 React以及其他框架应用中可能造成明显的性能问题——这正是推荐存扁平、取时还原模式的原因。循环引用如果 schema 和数据存在递归引用例如实体 A 引用实体 B、B 又引用 A反归一化时只有第一个被遇到的实体实例会被完整展开后续引用只会返回对应的id。这一点在源码中有明确体现src/index.jsunvisitEntity使用cache缓存每个实体先cache[schema.key][id] entityCopy占位再递归denormalize从而保证如果递归中再次引用到它引用已存在。2.5 底层实现unvisit 与缓存机制denormalize内部通过getUnvisit(entities)构建递归闭包src/index.js若 schema 是字面量对象/数组同样委托给ObjectUtils.denormalize/ArrayUtils.denormalize对null/undefined直接返回原值若命中EntitySchema且实体缺失会调用schema.fallback(id, schema)对应fallbackStrategy选项对集合类 schema 则调用其自身的denormalize。getEntitiessrc/index.js支持两种数据源普通 JS 对象entities[schemaKey][entityOrId]与 Immutable 数据通过entities.getIn([schemaKey, entityOrId.toString()])查询。Immutable 兼容性的判断与实现位于 src/schemas/ImmutableUtils.js通过检测对象上是否存在__ownerIDImmutable.Map或_map.__ownerIDImmutable.Record来识别 Immutable 实例且反归一化时会把键统一转成字符串再set回原对象规避 Immutable Map 写入时键被强转字符串的问题。三、schema 总览五种 Schema 与简写语法schema命名空间下共导出五个 Schema 类见 src/index.jsSchema用途简写语法schema.Array归一化一个由同构实体构成的数组[ mySchema ]schema.Entity定义一个实体类型无schema.Object定义普通对象映射其值需要归一化{ ... }schema.Union描述多种 Schema 的联合多态非集合场景无schema.Values描述值遵循给定 Schema 的 Map无Array、Union、Values三者共享多态基类PolymorphicSchemasrc/schemas/Polymorphic.js其核心是schemaAttributeschema 判别属性机制与inferSchema推断逻辑下文分别展开。所有 Schema 类都提供define(definition)实例方法用于与原始定义合并这在构建循环引用的 Schema 时必不可少。四、schema.Array(definition, schemaAttribute)数组归一化Array(definition, schemaAttribute)创建用于归一化数组的 Schema。若输入值不是数组而是Object归一化结果会是该对象各 value 组成的数组源码中getValues的实现见 src/schemas/Array.js。提示同样的行为可以直接用简写语法[ mySchema ]表达。4.1 参数definition必填。可以是单一 Schema表示数组中元素都是该 Schema也可以是schema 名 → Schema的映射表示数组中元素类型不唯一。schemaAttribute可选当definition为映射时必填。每个实体上用于决定采用映射中哪个 Schema 的属性。可以是字符串或函数若为函数依次接收value当前实体的输入值parent输入数组的父对象key输入数组在父对象上出现的键。4.2 实例方法define(definition)将传入的 definition 与构造时的原始 definition 合并常用于构建循环引用的 Schema。4.3 用法一单一实体类型的数组const data [{ id: 123, name: Jim }, { id: 456, name: Jane }]; const userSchema new schema.Entity(users); const userListSchema new schema.Array(userSchema); // or use shorthand syntax: const userListSchema [userSchema]; const normalizedData normalize(data, userListSchema);输出{ entities: { users: { 123: { id: 123, name: Jim }, 456: { id: 456, name: Jane } } }, result: [ 123, 456 ] }4.4 用法二多类型实体数组多态数组当输入数组包含多种实体类型时必须定义 schema 映射注意如果数据中出现了你没有提供映射的对象该原始对象会原样出现在 result 中且不会为其创建实体。const data [{ id: 1, type: admin }, { id: 2, type: user }]; const userSchema new schema.Entity(users); const adminSchema new schema.Entity(admins); const myArray new schema.Array( { admins: adminSchema, users: userSchema }, (input, parent, key) ${input.type}s ); const normalizedData normalize(data, myArray);输出{ entities: { admins: { 1: { id: 1, type: admin } }, users: { 2: { id: 2, type: user } } }, result: [ { id: 1, schema: admins }, { id: 2, schema: users } ] }注意多态输出的关键差异result中每个元素不再是单纯的 ID而是{ id, schema }对象schema字段记录了该元素实际命中的映射键供denormalize时还原用。ArraySchema.normalizesrc/schemas/Array.js还会过滤掉undefined/null的归一化结果其denormalize则逐元素调用denormalizeValuesrc/schemas/Polymorphic.js——单 Schema 模式下直接以 ID 查询多态模式下则读取value.schema定位映射 Schema 后再还原。五、schema.Entity(key, definition {}, options {})核心实体 SchemaEntity是 normalizr 中最核心、参数最丰富的 Schema用于定义实体类型及其中嵌套的实体关系。5.1 参数key必填。该类型实体在归一化结果中统一存放的键名必须是字符串。源码中会校验key必须是字符串否则抛出Expected a string key for Entity, but found ${key}.src/schemas/Entity.js。definition该实体内部嵌套实体的定义默认为空对象。你只需要声明那些存放嵌套实体的键其余值会原样复制到归一化后的实体输出中。options实体行为选项详见下表。5.2 options 详解选项类型默认值说明idAttributestring|functionid指定实体唯一 ID 所在属性。传函数时返回 ID 的值。该函数可能被执行多次因此生成结果必须每次一致——使用uuid之类的随机生成器会导致难以预料的错误mergeStrategy(entityA, entityB) entity将较新发现的实体合并到先前实体上{ ...entityA, ...entityB }遇到两个相同 ID 实体时的合并策略processStrategy(value, parent, key) entity返回输入实体的浅拷贝归一化前对实体的预处理可用来附加额外数据、补默认值、或彻底改造实体。建议始终返回输入的拷贝不要修改原始对象fallbackStrategy(key, schema) entity返回undefined反归一化时遇到 ID 引用缺失实体的兜底策略可返回一个占位实体各函数的参数说明processStrategy(value, parent, key)value为实体输入值parent为输入数组的父对象key为输入数组在父对象上出现的键mergeStrategy(entityA, entityB)entityA为先前已存储的实体entityB为新发现的实体fallbackStrategy(key, schema)key为缺失实体的 IDschema为缺失实体的 Schema。5.3 实例方法与实例属性实例方法define(definition)合并 definition 到原始定义用于循环引用场景。实例属性getterkey返回构造时传入的keyidAttribute返回构造时在 options 中传入的idAttribute。从源码看src/schemas/Entity.js构造函数会解构四个选项并做默认值处理idAttribute默认为字符串id通过getDefaultGetId包装成按属性取值同时兼容 Immutable 的.getmergeStrategy默认{ ...entityA, ...entityB }processStrategy默认(input) ({ ...input })fallbackStrategy默认返回undefined。5.4 综合用法示例const data { id_str: 123, url: https://twitter.com, user: { id_str: 456, name: Jimmy } }; const user new schema.Entity(users, {}, { idAttribute: id_str }); const tweet new schema.Entity( tweets, { user: user }, { idAttribute: id_str, // Apply everything from entityB over entityA, except for favorites mergeStrategy: (entityA, entityB) ({ ...entityA, ...entityB, favorites: entityA.favorites }), // Remove the URL field from the entity processStrategy: (entity) omit(entity, url) } ); const normalizedData normalize(data, tweet);输出{ entities: { tweets: { 123: { id_str: 123, user: 456 } }, users: { 456: { id_str: 456, name: Jimmy } } }, result: 123 }可以看到tweets实体中的嵌套user被替换为 ID456users实体单独成表processStrategy剥离了url字段result则是 tweet 自身的 ID。5.5 idAttribute 的函数用法当idAttribute传入函数时函数必须返回 ID 的值不是键名。例如当同一用户有id和可选的guest_id时可以组合两者生成复合 IDconst data [{ id: 1, guest_id: null, name: Esther }, { id: 1, guest_id: 22, name: Tom }]; const patronsSchema new schema.Entity(patrons, undefined, { // idAttribute *functions* must return the ids **value** (not key) idAttribute: (value) (value.guest_id ? ${value.id}-${value.guest_id} : value.id) }); normalize(data, [patronsSchema]);输出{ entities: { patrons: { 1: { id: 1, guest_id: null, name: Esther }, 1-22: { id: 1, guest_id: 22, name: Tom }, } }, result: [1, 1-22] }注意由于两个输入对象都有id: 1若不自定义idAttribute后一个会通过mergeStrategy合并覆盖前一个这里用函数生成了不同 ID两条数据得以共存。5.6 fallbackStrategy 用法缺失实体兜底当denormalize遇到引用但实体字典中不存在的 ID 时默认返回undefinedfallbackStrategy允许你生成一个占位实体避免下游空指针const users { 1: { id: 1, name: Emily, requestState: SUCCEEDED }, 2: { id: 2, name: Douglas, requestState: SUCCEEDED } }; const books { 1: {id: 1, name: Book 1, author: 1 }, 2: {id: 2, name: Book 2, author: 2 }, 3: {id: 3, name: Book 3, author: 3 } }; const authorSchema new schema.Entity(authors, {}, { fallbackStrategy: (key, schema) { return { [schema.idAttribute]: key, name: Unknown, requestState: NONE }; } }); const bookSchema new schema.Entity(books, { author: authorSchema }); denormalize([1, 2, 3], [bookSchema], { books, authors: users })输出[ { id: 1, name: Book 1, author: { id: 1, name: Emily, requestState: SUCCEEDED } }, { id: 2, name: Book 2, author: { id: 2, name: Douglas, requestState: SUCCEEDED }, }, { id: 3, name: Book 3, author: { id: 3, name: Unknown, requestState: NONE }, } ]这里第 3 本书引用了author: 3但authors字典中没有 ID 为3的用户fallbackStrategy便生成了一个{ id: 3, name: Unknown, requestState: NONE }占位实体。该调用链对应源码 src/index.jsunvisitEntity中getEntity(id, schema)返回undefined且命中EntitySchema时会执行schema.fallback(id, schema)。5.7 归一化内部流程visitedEntities 防循环EntitySchema.normalizesrc/schemas/Entity.js的流程是先用idAttribute取 ID → 借助visitedEntities[entityType][id]记录已访问的输入对象引用若当前输入对象已被访问过则直接返回 ID防止循环引用无限递归→ 调用processStrategy预处理 → 遍历this.schema中声明了嵌套 Schema 的键对每个对象值递归visit→ 最后addEntity写入实体字典并返回 ID。若 schema 中的值是函数支持惰性 schema会先以input调用解析出真实 Schemasrc/schemas/Entity.js。六、schema.Object(definition)普通对象映射Object(definition)定义普通对象映射其值需要归一化为实体。提示同样的行为可以用简写语法{ ... }直接表达。6.1 参数definition必填。对象内嵌套实体的定义默认为空对象。只需要声明存放实体的键其余值会原样复制到归一化输出。6.2 实例方法define(definition)合并 definition用于循环引用场景。6.3 用法// Example data response const data { users: [{ id: 123, name: Beth }] }; const user new schema.Entity(users); const responseSchema new schema.Object({ users: new schema.Array(user) }); // or shorthand const responseSchema { users: new schema.Array(user) }; const normalizedData normalize(data, responseSchema);输出{ entities: { users: { 123: { id: 123, name: Beth } } }, result: { users: [ 123 ] } }从源码看src/schemas/Object.jsObject的归一化会对 schema 中的每个键调用visit(input[key], input, key, ...)且归一化结果为null/undefined的键会直接从输出对象中删除delete object[key]这保证result里不会残留空值引用denormalize时则只对值非空! null的键进行还原src/schemas/Object.js。七、schema.Union(definition, schemaAttribute)非集合多态Union(definition, schemaAttribute)描述多个 Schema 的联合。当你需要schema.Array或schema.Values提供的多态行为、但目标字段并非集合时使用。7.1 参数definition必填。输入对象中嵌套实体的定义映射schema 名 → Schema。schemaAttribute必填。每个实体上决定采用哪个 Schema 的属性。可为字符串或函数若为函数依次接收value、parent、key。与Array/Values不同Union的schemaAttribute没有默认值——源码src/schemas/Union.js在构造时若未提供会直接抛出Expected option schemaAttribute not found on UnionSchema.。7.2 实例方法define(definition)合并 definition。7.3 用法注意如果数据中出现了你没有提供映射的对象原始对象会原样出现在 result 中且不会为其创建实体。const data { owner: { id: 1, type: user, name: Anne } }; const user new schema.Entity(users); const group new schema.Entity(groups); const unionSchema new schema.Union( { user: user, group: group }, type ); const normalizedData normalize(data, { owner: unionSchema });输出{ entities: { users: { 1: { id: 1, type: user, name: Anne } } }, result: { owner: { id: 1, schema: user } } }注意result.owner同样被包装为{ id: 1, schema: user }schema字段记录判别属性值denormalize时据此选择users还是groups还原。八、schema.Values(definition, schemaAttribute)键值映射归一化Values(definition, schemaAttribute)描述一个值遵循给定 Schema的 Map适用于按任意键组织的对象如 ID 字典。8.1 参数definition必填。可以是单一 Schema表示所有值都是该 Schema也可以是映射表示值的类型不唯一。schemaAttribute可选当definition为映射时必填。判别属性可为字符串或函数函数依次接收value、parent、key。8.2 实例方法define(definition)合并 definition。8.3 用法一单一 Schemaconst data { firstThing: { id: 1 }, secondThing: { id: 2 } }; const item new schema.Entity(items); const valuesSchema new schema.Values(item); const normalizedData normalize(data, valuesSchema);输出{ entities: { items: { 1: { id: 1 }, 2: { id: 2 } } }, result: { firstThing: 1, secondThing: 2 } }result保留了原对象的键firstThing、secondThing值被替换为实体 ID。这正对应ValuesSchema.normalize的实现src/schemas/Values.js用reduce遍历Object.keys(input)对非空值调用normalizeValue保持键不变。8.4 用法二多类型映射当对象的值存在多种实体类型、且无法仅凭键名确定 Schema 时使用映射方式与schema.Union和schema.Array的多态用法一致注意如果数据中出现了你没有提供映射的对象原始对象会原样出现在 result 中且不会为其创建实体。const data { 1: { id: 1, type: admin }, 2: { id: 2, type: user } }; const userSchema new schema.Entity(users); const adminSchema new schema.Entity(admins); const valuesSchema new schema.Values( { admins: adminSchema, users: userSchema }, (input, parent, key) ${input.type}s ); const normalizedData normalize(data, valuesSchema);输出{ entities: { admins: { 1: { id: 1, type: admin } }, users: { 2: { id: 2, type: user } } }, result: { 1: { id: 1, schema: admins }, 2: { id: 2, schema: users } } }九、多态机制的底层统一实现Array、Union、Values的多态行为全部由PolymorphicSchemasrc/schemas/Polymorphic.js统一支撑理解它即可举一反三_schemaAttribute字符串形式的schemaAttribute会被包装为(input) input[schemaAttribute]函数形式则原样保存src/schemas/Polymorphic.jsisSingleSchema当未提供schemaAttribute时为true此时normalizeValue直接按单一 Schema 处理inferSchema多态模式下用getSchemaAttribute(value, parent, key)求出判别值再从定义映射中取出对应 Schema若取不到未提供映射normalizeValue直接返回原始值不创建实体——这正是文档中反复出现的未提供映射的对象原样返回警告的根源src/schemas/Polymorphic.js多态归一化结果统一包装为{ id: normalizedValue, schema: attr }denormalizeValue再据此还原src/schemas/Polymorphic.js。十、从 API 到实战与 Quick Start 的衔接本文是 docs/api.md 的完整展开与其配套的还有 docs/quickstart.md以博客文章为例演示 Schema 组合、docs/introduction.md 与 docs/faqs.md常见问题等文档。仓库还提供了可直接运行的示例examples/github/GitHub API 响应归一化示例配套schema.js与output.jsonexamples/relationships/带关系实体的归一化输入/输出对照examples/redux/与 Redux 集成的完整示例涵盖 actions、modules、selectors可对照docs/api.md中normalize/denormalize在真实状态管理中的用法。测试方面src/tests/index.test.js 与 src/schemas/tests/ 下的Array.test.js、Entity.test.js、Object.test.js、Union.test.js、Values.test.js为每个 API 提供了行为级验证typescript-tests/ 目录则给出了各 Schema 的 TypeScript 类型用法参考可与本文示例互相印证。总结掌握normalize/denormalize与五种 Schema 是使用 normalizr 的全部基础Entity定义实体与嵌套关系配合idAttribute、mergeStrategy、processStrategy、fallbackStrategy四个选项微调行为Array/Object/Values覆盖三种容器结构Union补足非集合多态而define方法让循环引用 Schema 成为可能。结合本文对 src/index.js、src/schemas/ 各实现文件的源码剖析你不仅能准确预测每一次归一化的输出还能理解多态包装、缺失实体兜底、Immutable 兼容与防循环缓存等机制从而在实际项目中设计出稳妥、可维护的数据层。【免费下载链接】normalizrNormalizes nested JSON according to a schema项目地址: https://gitcode.com/gh_mirrors/no/normalizr创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表