ARTICLE DETAIL

资讯详情

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

Relay 数据获取哲学:从 Thinking in Relay 看声明式组件化数据依赖

Relay 数据获取哲学:从 Thinking in Relay 看声明式组件化数据依赖 Relay 数据获取哲学从 Thinking in Relay 看声明式组件化数据依赖【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relayRelay 将 React 的组件化与声明式思想延伸到了数据获取层组件只声明我需要什么数据而由框架静态地、自动地把整棵组件子树的数据需求合并成一次网络请求。本文基于仓库中 thinking-in-relay.md 的核心脉络结合源码与官方 Guided Tour 文档带你掌握 Fragment、Query、useFragment、useLazyLoadQuery与数据掩蔽Data Masking这套完整实践理解为什么 Relay 是声明式数据获取的框架而非普通 GraphQL 客户端。从 React 到 Relay把声明式思想迁移到数据层Relay 的数据获取方式深受 React 经验启发。React 将复杂的界面拆解为可复用的组件components让开发者能够隔离地思考应用的离散单元同时降低应用不同部分之间的耦合更重要的是这些组件是**声明式declarative**的——开发者只需描述给定某个状态UI 应该长什么样而无需关心如何去改变 UI。不同于以往用命令式指令操作原生视图如 DOM的做法React 通过一份 UI 描述自动推导出所需执行的命令。Relay 把同样的思想带到了数据层组件声明自己需要的数据Relay 负责推导并执行获取数据的命令。这正是理解整个框架的起点后续的 Fragment、Query、Data Masking 等概念都是这一思想的直接产物。为视图获取数据两种朴素方案各自的缺陷绝大多数产品的需求非常一致在显示 loading 指示器的同时获取一个视图层级所需的全部数据待数据就绪后一次性渲染整个视图。围绕如何获取视图数据有两种看似合理的朴素方案但都存在明显问题方案一由根组件统一声明并获取自己和所有子组件的数据。这引入了强耦合任何子组件的数据需求发生变化都必须去修改所有可能渲染它的根组件。耦合意味着更高的出 Bug 概率也会拖慢开发节奏。方案二每个组件各自声明并获取自己所需的数据。听起来很理想但问题在于组件可能根据它收到的数据去渲染不同的子组件于是嵌套组件只有在父组件的查询完成之后才能开始渲染和获取数据。也就是说数据获取被迫分阶段串行进行先渲染根并获取它的数据再渲染子组件并获取它们的数据一直推进到叶子组件——渲染过程将产生多次慢速、串行的网络往返waterfall。Relay 把两种方案的优势结合起来允许组件各自指定数据需求但把这些需求静态合并coalesce成一个查询一次性获取整棵组件子树的数据。关键在于Relay 是在代码编写时应用运行之前静态地确定整个视图的数据需求的而不是在运行时动态拼接。这一能力建立在 GraphQL 之上函数组件用一个或多个 GraphQL Fragment 描述数据需求Fragment 彼此嵌套最终嵌套进 Query当该 Query 被获取时Relay 会为它及其所有嵌套 Fragment 发出单次网络请求。用 Fragment 声明组件的数据需求在 Relay 中组件的数据需求通过Fragment指定。Fragment 是一段有名字的 GraphQL 片段声明从某个特定类型的对象上选择哪些字段使用graphql字面量书写。例如下面的AuthorDetails_author片段声明了要获取作者的名字和照片 URL// AuthorDetails.react.js const authorDetailsFragment graphql fragment AuthorDetails_author on Author { name photo { url } } ;随后在函数组件中调用useFragment(...)钩子从 store 中读出这些数据。真正从哪个作者身上读由传给useFragment的第二个参数决定// AuthorDetails.react.js export default function AuthorDetails(props) { const data useFragment(authorDetailsFragment, props.author); // ... }关于useFragment有几个关键点值得展开Fragment Reference片段引用第二个参数props.author是一个片段引用。它不是数据本身而是一个指向特定类型实例的指针——Relay 用它去 store 中读取该片段声明的字段。引用通过把 Fragmentspread进另一个 Fragment 或 Query 得到。Fragment 不能单独被获取所有 Fragment 最终都必须直接或传递地spread 进某个 Query数据才会真正被拉取。这一点在 fragments.md 中被反复强调Fragments cant be fetched by themselves。自动订阅更新使用useFragment渲染的组件会自动订阅该 Fragment 数据的更新当数据在应用任何位置被修改如新数据到达、mutation 提交组件会自动使用最新数据重新渲染。命名约定Fragment 名需要全局唯一官方约定按模块名 属性名命名module_name_property_name例如AuthorDetails_author。这样既便于定位 Fragment 定义在哪个模块也避免同模块内多个 Fragment 的命名冲突。生成类型Relay 编译器为每个 Fragment 自动生成 Flow/TS 类型其中带$key后缀的类型表示片段引用如AuthorDetails_author$key带$data后缀的类型表示数据形状可从编译产物fragment_name.graphql.js中导入。从源码看useFragment的实现路径是先通过getFragment把graphql标签节点解析为FragmentNode再委托给useFragmentInternal完成实际的读取与订阅逻辑并依据特性开关在useFragmentInternal_CURRENT当前实现与useFragmentInternal_EXPERIMENTAL实验实现之间切换参见 packages/react-relay/relay-hooks/useFragment.js 与 packages/react-relay/relay-hooks/useFragmentInternal.js。在开发模式下它还会通过useDebugValue暴露当前渲染的 fragment 名称与数据便于调试。用 Query 聚合 Fragment一次请求渲染整个视图只有 Fragment 还不够——它们必须被聚合进 Query 才能真正获取数据。假设我们要渲染一篇 Story 的标题和作者详情可以声明一个 spread 了AuthorDetails_author的查询// Story.react.js const storyQuery graphql query StoryQuery($storyID: ID!) { story(id: $storyID) { title author { ...AuthorDetails_author } } } ;然后用useLazyLoadQuery获取并渲染它// Story.react.js function Story(props) { const data useLazyLoadQuery(storyQuery, props.storyId); return ( Heading{data?.story.title}/Heading {data?.story?.author AuthorDetails author{data.story.author} /} /); }注意这里发生了什么我们发出的单次网络请求同时包含了Story组件和AuthorDetails组件所需的数据数据就绪后整个视图可以一次性渲染完成无需任何串行往返。data.story.author若存在默认所有字段都可空就是可以传给AuthorDetails的 fragment reference——useLazyLoadQuery返回的查询结果天然充当其内部所有子 fragment 的引用来源。关于查询类 APIqueries.md 给出了完整的三种模式可以按场景选用useLazyLoadQuery(query, variables)组件渲染时才惰性发起获取是起步最简单的 API。但文档明确提示不加节制地使用惰性加载容易引发嵌套式/瀑布式往返降低性能。usePreloadedQuery(queryRef, ...)loadQuery/useQueryLoader推荐的render-as-you-fetch模式——在组件渲染之前如路由切换的事件处理器中、应用初始化阶段就发起获取拿到一个PreloadedQuery引用再传给渲染组件。这能把请求提前到渲染之前尽早展示内容也便于配合 React Suspense 设计加载态。注意loadQuery在 React 渲染阶段调用会直接抛错。Suspense 集成查询组件是可挂起的组件配合Suspense fallback{...}即可声明式地展示 loading 占位 UI避免闪烁参见 loading-states.md。useLazyLoadQuery的签名与选项在源码中有完整注释packages/react-relay/relay-hooks/useLazyLoadQuery.js其中fetchPolicy控制缓存与网络请求的组合策略默认值为store-or-network优先复用本地缓存仅当数据缺失时才发请求其余可选值包括取值行为store-or-network默认复用本地缓存仅在数据缺失时发网络请求完全命中缓存则不发请求store-and-network复用本地缓存但无论缓存是否完整都总是发网络请求network-only忽略本地缓存总是发网络请求store-only只用本地缓存永不发网络请求适合读取纯本地数据此外fetchKey可强制组件在重新渲染时重新求值查询与变量networkCacheConfig默认{force: true}用于绕过网络层的额外响应缓存。数据掩蔽Data Masking消灭隐式依赖在典型的数据获取方式中两个组件之间经常存在隐式依赖implicit dependencies。例如Story /可能使用了某份数据却并没有直接确保它被获取——这份数据恰好由系统其他部分比如AuthorDetails /拉取。一旦修改AuthorDetails /并删除了那段数据获取逻辑Story /就会毫无预兆地坏掉。这类 Bug 往往不会立刻显现尤其在大团队维护的大型应用中手工测试与自动化测试能帮上的忙有限——这正是适合交给框架来解决的系统性问题。Relay 为此提供了两个层次的保障最小可见性data masking组件只能访问自己在 GraphQL Fragment 中明确请求的字段除此之外什么都看不到。查询了 Storytitle的组件看不到别人查询的text更严格的是父组件连子组件请求的数据也看不到——否则同样会破坏封装。不透明引用的校验Relay 使用 props 上的不透明标识fragment reference来验证渲染组件前已显式获取其数据。如果Story /渲染AuthorDetails /却忘了 spread 它的 fragmentRelay 会告警数据缺失哪怕别的组件碰巧获取了相同的数据Relay 依然会告警——这告诉我们现在也许能跑但将来极可能坏掉。fragments.md 对组合场景的剖析很透彻父组件UserComponent同时渲染并 spread 子组件UsernameSection的 fragment传给子组件的 fragment reference本身不携带子组件声明的任何数据子组件内部用useFragment自行读取。正因如此任何组件都无法——哪怕是意外地——对其他组件产生隐式依赖开发者可以放心地局部修改组件而不用担心波及他人。背后的机制归一化缓存与运行时支撑一次请求拿全部数据与组件各自声明数据看似矛盾之所以能同时成立是因为 Relay 的运行时架构详见 runtime-architecture.md 与 architecture-overview.md归一化对象图缓存Relay Runtime 维护一个以DataID为键、以Record为值的RecordSource作为缓存。层级化的 GraphQL 响应被展平成记录每个服务器实体无论被多少个查询获取都只存储一份__ref链接用于表达记录间的引用包括循环图。编译器与运行时解耦Relay 编译器负责把graphql字面量编译为运行时消费的标准产物运行时则提供归一化缓存、优化后的 write/read 操作、垃圾回收、乐观更新与订阅等能力React/Relay 只是构建在运行时之上的高层产品 API如useFragment。视图一致性Relay 维护每个 UI 视图 → 它引用的 ID 集合的映射写入缓存时只通知订阅了受影响 ID 的视图重新渲染未受影响的视图跳过重渲染——这与 Data Masking 共同构成了局部推理、全局一致的保证。也就是说Think in Relay 所讲的静态合并数据需求、单次请求、数据掩蔽最终都落地在编译器与归一化运行时这两个坚实的基础上而不是魔法。总结GraphQL 为构建高效、解耦的客户端应用提供了强大工具Relay 则在其上构建起一套声明式数据获取的框架通过把取什么数据what与怎么取how分离组件各自用 Fragment 声明需求框架静态合并为单次查询、用数据掩蔽保证封装、用归一化缓存保证一致性让应用在默认情况下就健壮、透明且高性能。React、Relay、GraphQL 单独来看都很强大而它们的组合正是一个能让你快速前进、规模化交付高质量应用的 UI 平台。延伸阅读Thinking in GraphQLREST 到归一化缓存的演进Guided TourFragment 的声明、组合与渲染Guided TourQuery 的三种获取模式与 render-as-you-fetchGuided Tour用 Suspense 渲染加载态运行时架构归一化缓存与 Store 操作架构总览编译器、运行时与 React/Relay 三层【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relay创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表