Relay 交互查询实战:用 Query Variables 与 Preloaded Queries 优化二次数据获取 前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载本指南源自 Relay 官方教程系列website/versioned_docs/version-v19.0.0/tutorial/queries-2.md聚焦同一屏幕上由用户交互触发的第二次查询这一核心场景。文章以 hovercard悬停卡片为实战案例完整讲解如何使用Query Variables向服务端动态传参以及如何使用Preloaded Queries将网络请求提前到事件处理函数中发起从而让交互响应更快、首屏更轻。读完本文你将掌握useLazyLoadQuery、usePreloadedQuery与useQueryLoader三个 Hook 的适用场景与完整改造流程并理解 Relay 按节点缓存Relay Store的底层原理。为什么同一屏幕还需要第二个查询在 queries-1 中我们了解到fragment 让每个组件各自声明数据需求而在运行时 Relay 却只为整个屏幕执行一次查询。Relay 的设计原则可以进一步概括为一个用户交互最多对应一次查询。导航到另一个屏幕只是用户交互中最常见的一种。但在同一个屏幕内有些交互会额外披露初始展示之外的数据。如果某个交互发生频率较低、却需要获取大量额外数据那么在交互发生时发起第二次查询而不是在首屏加载时一次性全部拉取往往是更聪明的做法——它能让首屏加载更快、成本更低。还有一些交互的数据量是无限的例如hovercard 中的 hovercard根本无法静态预知。另外如果数据优先级较低、希望主数据加载完成后再自动弹出无需用户额外输入Relay 提供了deferred fragment延迟 fragment特性将在教程后续章节介绍。本篇文章聚焦的则是由用户交互显式触发的二次查询。搭建 hovercard 场景教程项目已经准备了一个 hovercard 组件但由于它使用了ImageFragment此时还未编译一直被放在名为future的目录中避免编译错误。现在可以把这些模块移入src/componentsmv future/* src/components如果你完成了 queries-1 的练习PosterByline组件应当类似这样export default function PosterByline({ poster }: Props): React.ReactElement { const data useFragment(PosterBylineFragment, poster); return ( div classNamebyline Image image{data.profilePicture} width{60} height{60} classNamebyline__image / div classNamebyline__name{data.name}/div /div ); }接下来集成 hovercard 组件修改PosterBylineimport Hovercard from ./Hovercard; import PosterDetailsHovercardContents from ./PosterDetailsHovercardContents; const {useRef} React; export default function PosterByline({ poster }: Props): React.ReactElement { const data useFragment(PosterBylineFragment, poster); const hoverRef useRef(null); return ( div ref{hoverRef} classNamebyline Image image{data.profilePicture} width{60} height{60} classNamebyline__image / div classNamebyline__name{data.name}/div Hovercard targetRef{hoverRef} PosterDetailsHovercardContents / /Hovercard /div ); }此时悬停任何人的名字都会出现一个带更多信息的 hovercard。打开PosterDetailsHovercardContents.tsx可以看到它通过useLazyLoadQuery在该组件挂载时执行了第二次查询。但这里存在一个问题无论悬停哪个 posterhovercard 显示的始终是同一个人原因就在PosterDetailsHovercardContents.tsx中的查询里——node字段的id参数被硬编码成了1const PosterDetailsHovercardContentsQuery graphql query PosterDetailsHovercardContentsQuery { node(id: 1) { ... on Actor { ...PosterDetailsHovercardContentsBodyFragment } } } ;这里涉及两个需要理解的概念node字段schema 中定义的顶层字段给定任意图节点的唯一 ID 即可获取该节点。它当前把 ID 硬编码了我们的目标是把它替换成由 UI 状态提供的变量。... on Actor类型细化type refinement由于node字段可能接收任意 ID静态上无法知道选中的节点是什么类型类型细化声明了我们期望的类型从而允许使用Actor类型上的字段。下一节教程会深入讲解这里先按模式使用即可。Query Variables把运行时数据传给服务端我们需要告诉服务端要的是哪个人的更多信息。GraphQL 允许定义query variables查询变量它们可以像参数一样传给特定字段并在服务端可用。在 queries-1 中我们看到字段可以接收参数但参数值是硬编码的例如url(width: 200, height: 200)。借助查询变量我们可以在运行时决定这些值。变量随查询一起从客户端传给服务端并且始终以$美元符号开头。Step 1 — 声明查询变量修改查询声明它接收一个查询变量const PosterDetailsHovercardContentsQuery graphql query PosterDetailsHovercardContentsQuery( $posterID: ID! ) { node(id: 1) { ... on Actor { ...PosterDetailsHovercardContentsBodyFragment } } } ;要点变量名是$posterID这是查询其余部分引用 UI 传入值的符号变量有类型——这里是ID!。ID类型是String的同义词专门用于节点 ID以与普通字符串区分!表示该字段非空non-nullable。在 GraphQL 中字段默认可空非空才是例外。Step 2 — 把变量作为字段参数传入把硬编码的1替换成变量const PosterDetailsHovercardContentsQuery graphql query PosterDetailsHovercardContentsQuery($posterID: ID!) { node( id: $posterID ) { ... on Actor { ...PosterDetailsHovercardContentsBodyFragment } } } ;:::note 查询变量不仅能作为字段参数还能作为fragment 的参数fragment arguments使用。 :::Step 3 — 向 useLazyLoadQuery 提供变量值useLazyLoadQueryHook 的第二个参数是一个包含变量值的对象。给组件新增一个 prop 并把它的值传进去export default function PosterDetailsHovercardContents({ posterID, }: { posterID: string; }): React.ReactElement { const data useLazyLoadQueryQueryType( PosterDetailsHovercardContentsQuery, {posterID}, ); return PosterDetailsHovercardContentsBody poster{data.node} /; }Step 4 — 从父组件传入 ID现在需要从 hovercard 的父组件PosterByline提供posterID。先在它的 fragment 中加入id再作为 prop 传下去const PosterBylineFragment graphql fragment PosterBylineFragment on Actor { id ... } ;export default function PosterByline({ poster }: Props): React.ReactElement { ... return ( ... PosterDetailsHovercardContents posterID{data.id} / ... ); }完成以上四步后hovercard 就能正确显示每个被悬停 poster 的信息了打开浏览器的 Network 面板可以确认变量值是与查询一起被发送到服务端的你可能还会注意到这个请求只在第一次悬停某个 poster 时发出。Relay 会缓存查询结果并复用直到缓存在一段时间未被使用后被回收。为什么 GraphQL 需要变量的语法你可能会疑惑为什么 GraphQL 不直接把变量值插值进查询字符串里原因有两个层面运行时根本没有查询字符串。如 queries-1 所述Relay 编译器会把所有graphql标记的字符串预编译并替换成更高效的数据结构运行时不保留查询文本。持久化查询persisted queries。你可以配置 Relay 在构建时把每条查询上传到服务端并分配一个 ID参见 guides/persisted-queries.md。运行时 Relay 只需告诉服务端给我查询 #1337字符串插值自然不可能变量必须带外out-of-band传递。即便查询字符串可用把变量值分开传递也能避免序列化任意值、转义字符串等麻烦超出任何 HTTP 请求本就需要的处理。Relay 缓存与 Relay Store与大多数系统不同Relay 的缓存不是基于查询而是基于图节点。Relay 维护一个本地缓存存放所有已获取的节点称为Relay Store。Store 中每个节点由其 ID 标识和检索。如果两个查询请求了相同的信息以节点 ID 标识第二个查询会直接用第一个查询得到的缓存数据来满足而不会再次发起网络请求。要利用这一缓存行为请务必配置 missing field handlers缺失字段处理器。Relay 还会对 Store 中的节点进行垃圾回收如果节点不再被任何正在使用或最近被挂载组件使用的查询所可达就会被回收。从源码层面看缓存行为的起点是useLazyLoadQuery内部的useLazyLoadQueryNodepackages/react-relay/relay-hooks/useLazyLoadQueryNode.js它通过getQueryResourceForEnvironment(environment)拿到当前环境对应的QueryResource再调用QueryResource.prepareWithIdentifier(...)按查询缓存标识getQueryCacheIdentifier由 environment、query、fetchPolicy、renderPolicy 与 cacheBreaker 共同计算读取或发起数据。这意味着相同 ID 的节点数据会被 Store 去重复用而fetchPolicy决定是否/何时发起网络请求。Preloaded Queries在交互发生时就发起请求示例应用很简单性能不是问题实际上服务端被人为放慢了速度以便让加载状态可感知。但 Relay 的核心目标之一是让真实应用尽可能快。当前 hovercard 用useLazyLoadQuery查询在组件渲染时才发起时间线大致是理想的状况是尽早开始网络请求但这里要等 React 渲染完成。如果再用React.lazy延迟加载 hovercard 组件的代码时间线会更糟得先加载组件代码、再渲染、再发请求中间空等更久。更优的时间线应该是——在鼠标事件处理函数一开始就发起查询同时并行开始渲染组件必要时先拉取代码两个异步过程都完成后用已就绪的数据渲染并展示Relay 提供的preloaded queries预加载查询正是为此设计的。下面把 hovercard 改造成预加载查询。Step 1 — 把 useLazyLoadQuery 换成 usePreloadedQuery回顾当前组件它现在在渲染时才拉数据export default function PosterDetailsHovercardContents({ posterID, }: { posterID: string; }): React.ReactElement { const data useLazyLoadQueryQueryType( PosterDetailsHovercardContentsQuery, {posterID}, ); return PosterDetailsHovercardContentsBody poster{data.node} /; }useLazyLoadQuery以variables作为第二个参数。改成usePreloadedQuery后变量的取值时机变成了查询被 fetch 时早于该组件渲染因此这个 Hook 接收的是一个query reference查询引用——它包含了取回查询结果所需的信息。查询引用将在 Step 2 中创建。import {usePreloadedQuery} from react-relay; import type {PreloadedQuery} from react-relay; import type {PosterDetailsHovercardContentsQuery as QueryType} from ./__generated__/PosterDetailsHovercardContentsQuery.graphql; export default function PosterDetailsHovercardContents({ queryRef, }: { queryRef: PreloadedQueryQueryType, }): React.ReactElement { const data usePreloadedQuery( PosterDetailsHovercardContentsQuery, queryRef, ); ... }:::note 父组件PosterByline需要发起PosterDetailsHovercardContentsQuery查询因此该查询必须被导出export const PosterDetailsHovercardContentsQuery graphql...:::Step 2 — 在父组件中使用 useQueryLoader现在PosterDetailsHovercardContents期望拿到一个查询引用我们需要创建它并从父组件PosterByline传下去。创建查询引用使用useQueryLoaderHook它同时返回一个用于在事件处理器中触发查询的函数import {useQueryLoader} from react-relay; import type {PosterDetailsHovercardContentsQuery as HovercardQueryType} from ./__generated__/PosterDetailsHovercardContentsQuery.graphql; import {PosterDetailsHovercardContentsQuery} from ./PosterDetailsHovercardContents; export default function PosterByline({ poster }: Props): React.ReactElement { ... const [ hovercardQueryRef, loadHovercardQuery, ] useQueryLoaderHovercardQueryType(PosterDetailsHovercardContentsQuery); return ( ... PosterDetailsHovercardContents queryRef{hovercardQueryRef} / ... ); }useQueryLoader返回两样我们需要的东西查询引用query ref一段不透明信息usePreloadedQuery会用它在渲染时取回查询结果loadHovercardQuery一个会发起请求的函数。Step 3 — 在事件处理器中发起查询最后在卡片显示时的事件处理器里调用loadHovercardQuery。Hovercard组件恰好提供了onBeginHover事件export default function PosterByline({ poster }: Props): React.ReactElement { ... const [ hovercardQueryRef, loadHovercardQuery, ] useQueryLoaderHovercardQueryType(PosterDetailsHovercardContentsQuery); function onBeginHover() { loadHovercardQuery({posterID: data.id}); } return ( div classNamebyline ... Hovercard onBeginHover{onBeginHover} targetRef{hoverRef} PosterDetailsHovercardContents queryRef{hovercardQueryRef} / /Hovercard /div ); }注意查询变量现在是在发起请求的地方传入的{posterID: data.id}。完成改造后行为与之前一致但由于 Relay 能更早地启动查询交互响应会快一些。:::tip 尽管为了讲解简单本文先用useLazyLoadQuery引入查询但preloaded queries 始终是 Relay 中推荐使用查询的方式因为它能在真实世界中显著提升性能。配合服务端与路由系统的集成你甚至可以在下载或运行任何客户端代码之前就在服务端预加载网页的主查询。 :::从源码理解三个 Hook 的分工三个 Hook 在 packages/react-relay/relay-hooks 目录下的实现可以帮助我们理解它们的分工与差异useLazyLoadQueryuseLazyLoadQuery.js接收gqlQuery、variables和可选的options含fetchPolicy、fetchKey、networkCacheConfig等在渲染期间调用内部fetchQuery(environment, query)发起请求。其源码注释明确提示该 Hook 在渲染时才启动数据获取可能引发多次嵌套或瀑布式往返请求从而降低性能应优先使用usePreloadedQuery。usePreloadedQueryusePreloadedQuery.js接收gqlQuery与preloadedQuery查询引用从引用中解出variables、fetchPolicy、source等。如果source存在且 environment 匹配会复用预加载阶段已经发起的 observablesource.ifEmpty(fallbackFetchObservable)避免重复请求若 environment 不一致或 source 为空则回退到渲染时重新执行查询。useQueryLoaderuseQueryLoader.js实际返回三元组[queryReference, loadQuery, disposeQuery]。其中loadQuery回调内部调用loadQuery(environment, preloadableRequest, variables, options)实现见 loadQuery.js生成新的查询引用并触发请求同时 Hook 内部维护undisposedQueryReferencesRef集合通过useEffect在每次提交时 dispose 掉旧的查询引用、在卸载时清理全部剩余引用确保不会泄漏 Relay Store 中的数据。教程中只使用了前两个返回值第三个disposeQuery可用于手动清除当前查询引用。三者共用useLazyLoadQueryNodeuseLazyLoadQueryNode.js作为统一的数据读取节点无论查询是渲染时才取还是已预加载最终都经由QueryResource.prepareWithIdentifier从 Relay Store 读取数据并通过 React Suspense 呈现加载状态——这正是为什么三种写法能共享同一套缓存与加载语义。总结虽然屏幕上初始展示的全部数据应合并为一次查询但需要进一步信息的用户交互可以用第二次查询来处理。Query Variables让你把信息随查询一起传给服务端变量通过$前缀声明、带类型如ID!并通过字段参数使用。Preloaded Queries 始终是最佳实践对用户交互查询在事件处理器中发起 fetch对屏幕的初始查询在路由系统允许的情况下尽早发起。useLazyLoadQuery仅用于快速原型或干脆不用。了解usePreloadedQuery与useQueryLoader的分工查询引用 vs 加载函数以及 Relay Store 按节点缓存的原理是写出高性能 Relay 应用的关键。下一篇教程将介绍如何通过类型细化增强 hovercard——对不同类型的 poster 做差异化处理之后还会看到初始查询中需要更新、并用不同变量重新获取的数据该如何处理。如果想继续深入可阅读本仓库中的 filling-in-missing-data.md、persisted-queries.md以及 packages/react-relay/relay-hooks 下的 Hook 实现源码。赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐Relay 交互查询实战从 Query Variables 到 Preloaded Queries 的完整指南Relay 交互查询实战从 Query Variables 到 Preloaded Queries 的完整指南 Relay 的核心哲学是一次用户交互最多只发前端开发工具Relay 交互查询实战Query Variables 与 Preloaded Queries 深度解析v14 教程Relay 交互查询实战Query Variables 与 Preloaded Queries 深度解析v14 教程 本篇技术指南基于 Relay 官方教前端开发工具Relay 查询刷新Refreshing Queries实战指南用 useQueryLoader 与 useLazyLoadQuery 拉取最新数据Relay 查询刷新Refreshing Queries实战指南用 useQueryLoader 与 useLazyLoadQuery 拉取最新数据 本篇前端开发工具上一篇Claude Desktop for Linux Code Tab 工作流测试指南预览窗格、PR 监控、Worktree 隔离与侧边聊天的验证体系下一篇blesschess深度解析从代码到棋盘传统棋类数字化的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考