尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Ferry normalize 包源码解析:5 个关键步骤讲透 GraphQL 数据规范化/反规范化核心原理

Ferry normalize 包源码解析:5 个关键步骤讲透 GraphQL 数据规范化/反规范化核心原理 Ferry normalize 包源码解析5 个关键步骤讲透 GraphQL 数据规范化/反规范化核心原理【免费下载链接】ferryStream-based strongly typed GraphQL client for Dart项目地址: https://gitcode.com/gh_mirrors/fer/ferryFerry 是一个基于流Stream-based的强类型 GraphQL 客户端Dart 生态而其normalize包正是整个缓存体系的核心引擎负责把 GraphQL 响应**规范化Normalize为按实体 ID 打平的键值缓存再在需要时反规范化Denormalize**还原成嵌套的查询树。读懂这个包你就掌握了 Ferry 实现跨查询缓存共享、局部更新与引用去重的底层原理。 先搞懂两个概念规范化与反规范化GraphQL 响应天然是嵌套树查询什么样就返回什么样而现代客户端缓存的理想形态是按实体扁平存储概念输入输出作用规范化 Normalize嵌套的 GraphQL 响应 JSONdataId → 实体对象的扁平 Map同一实体只存一份天然支持缓存共享与增量更新反规范化 Denormalize扁平 Map 查询文档嵌套的响应 JSON按$ref引用递归把树拼回来规范化后的缓存长这样$ref即引用键默认由kDefaultReferenceKey定义{ Query: { pokemon: { $ref: Pokemon:25 } }, Pokemon:25: { __typename: Pokemon, name: Pikachu, id: 25 } }两个查询只要引用同一个Pokemon:25就共享同一份数据——这就是缓存高效的秘密。️ 核心源码文件速查表normalize 包代码量不大主干就是两个入口 两个递归函数 一组工具全部位于packages/normalize/lib/src/下源码文件职责normalize_operation.dart规范化总入口normalizeOperation()denormalize_operation.dart反规范化总入口denormalizeOperation()normalize_node.dart递归遍历 AST把节点写入缓存核心算法denormalize_node.dart递归遍历 AST从缓存还原节点核心算法utils/resolve_data_id.dart为实体生成唯一dataIdutils/field_key.dart生成字段名参数的组合键utils/expand_fragments.dart按__typename展开内联/命名 Fragmentutils/deep_merge.dart新旧数据深合并utils/is_dangling_reference.dart判定$ref是否指向不存在的实体policies/type_policy.dart类型级定制keyFields、根类型标记policies/field_policy.dart字段级定制keyArgs/read/mergeconfig/normalization_config.dart贯穿全程的配置上下文对外 API 统一由 normalize.dart 导出。测试文件覆盖了packages/normalize/test/下的query_fragments/、query_variables/、field_policy/、evictions/等目录是很好的行为验证素材。 规范化流水线5 个关键步骤步骤 1可选地为查询注入__typename若调用normalizeOperation()时传入addTypename: true会用AddTypenameVisitor转换查询文档见utils/add_typename_visitor.dart给每个选择集补上__typename。没有__typename的实体无法被规范化——这是整个体系的基石。步骤 2定位操作定义与根类型入口函数normalizeOperation()normalize_operation.dart先通过getOperationDefinition()从文档中找到对应操作再用resolveRootTypename()结合TypePolicy确定根类型Query/Mutation/Subscription最终组装出NormalizationConfig配置上下文。步骤 3为每个实体解析唯一 dataIdresolveDataId()utils/resolve_data_id.dart是 ID 生成器优先级非常明确TypePolicy.keyFields自定义复合主键生成TypeName:{json}形式dataIdFromObject全局自定义解析函数兜底规则id或_id字段生成经典的TypeName:id格式以上都不满足则返回null——该实体不规范化直接以嵌套形式挂在父对象下。步骤 4展开 FragmentexpandFragments()utils/expand_fragments.dart是规范化能正确处理 Fragment 的关键它依据当前实体的__typename与 Fragment 的typeCondition含possibleTypes接口/联合类型推导匹配把内联片段与命名片段的选择集深度合并成扁平的字段列表并自动剔除被skip/include跳过的字段。步骤 5normalizeNode 递归写入缓存核心算法在normalize_node.dart的normalizeNode()逻辑可以概括为列表逐项递归叶子节点无子选择集直接返回标量值对象节点先解析dataId→ 读出现有缓存 → 用FieldKey为每个字段生成带参数值的键如friends(first:1)→ 检查部分数据缺失且无read/merge策略则抛PartialDataException除非allowPartialData→ 与现有数据deepMerge()深合并 →write(dataId, merged)写入缓存并只返回{$ref: dataId}这个引用占位符。 精妙之处非根对象写入后只留引用所以同一个实体出现在 10 个查询里也只占一份内存deepMerge则保证新响应与旧缓存字段级合并实现增量更新。 反规范化如何从扁平缓存反向还原树denormalizeOperation()denormalize_operation.dart同样先解析根类型然后交给denormalizeNode()denormalize_node.dart递归还原引用识别节点含$ref键时通过config.read(dataId)取回实体本体再递归取不到即抛出DanglingReferenceException悬空引用列表容错isDanglingReference()逐个过滤悬空项配合allowDanglingReference开关决定是跳过还是抛错部分数据字段缺失且无法读取时抛PartialDataExceptionreturnPartialData: true则静默跳过handleException: true时整个操作返回null而非崩溃虚拟化字段配置了FieldPolicy.read的字段即使缓存中不存在也能通过 read 函数凭空计算出来。另外还提供denormalizeFragment()用于只针对单个 Fragment 还原数据适合细粒度的局部读取。️ 两套定制策略TypePolicy 与 FieldPolicy规范化行为可通过两级策略精细控制定义见policies/目录TypePolicy类型级keyFields指定复合主键支持嵌套子字段queryType/mutationType/subscriptionType标记根类型空keyFields可声明此类型不规范化。FieldPolicy字段级keyArgs声明哪些参数才算字段身份空列表则完全忽略参数read自定义读取逻辑merge自定义新旧数据合并——对分页列表等场景尤为重要。✅ 一句话 API 速览// 规范化把响应写入缓存 normalizeOperation(write: write, read: read, document: document, data: response, variables: variables); // 反规范化从缓存按文档还原 final data denormalizeOperation(read: read, document: document, variables: variables);write/read是解耦的函数注入上层packages/ferry_cache缓存包与packages/ferry_store存储抽象含 Hive/SQLite 持久化正是基于这两个 API 构建读写链路。 总结Ferry 的normalize包用不到 30 个文件讲清了一件事规范化 解析 dataId → 展开 Fragment → 递归 deepMerge → 写入$ref反规范化 沿 AST 递归 → 遇到$ref就查表还原。配合TypePolicy/FieldPolicy两套策略它既兼容 Relay 规范化的经典心智模型又为复合主键、虚拟化字段、部分数据等现实问题留足了扩展点。想深入验证行为建议直接跑packages/normalize/test/下的分类测试从query/simple_test.dart读起一路到field_policy/与evictions/dangling_reference_test.dart即可完整复现本文讲到的全部机制。【免费下载链接】ferryStream-based strongly typed GraphQL client for Dart项目地址: https://gitcode.com/gh_mirrors/fer/ferry创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表