首页/新闻资讯/正文详情

Relay 数据分页实战指南:usePaginationFragment Hook 深度解析

发布时间:2026/9/23 1:45:23 来源:云帆数科 栏目:资讯中心
Relay 数据分页实战指南:usePaginationFragment Hook 深度解析
Relay 数据分页实战指南usePaginationFragment Hook 深度解析【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relayusePaginationFragment是 Relay 提供的用于分页渲染connection数据的 React Hook它让你可以在组件内直接渲染一个带有 connection 的 fragment并通过loadNext/loadPrevious前后双向翻页。本文以 Relay 官方 API 参考文档为骨架结合当前仓库的 Hook 实现与测试源码完整讲解其参数、返回值、内部工作机制以及与旧版PaginationContainer的差异帮助你写出可正确分页、可安全 refetch、不会意外 suspend 的 React 组件。认识usePaginationFragment一个 Hook 完成连接分页在 Relay 中连接connection是一套标准化的 GraphQL 分页规范见 Connections 规范服务器通过edges/node/pageInfo结构返回列表客户端用first/after向前分页或last/before向后分页参数控制翻页。传统上开发者需要自己维护当前光标在哪一页、还要不要继续加载这类状态而usePaginationFragment把这部分逻辑完全封装进了 Hook读取 fragment 数据并自动订阅 store 更新通过refetchable指令自动生成分页查询无需手写分页 query暴露loadNext/loadPrevious/hasNext/hasPrevious/isLoadingNext/isLoadingPrevious等状态开箱即用地支持同时双向分页提供refetch用于以新变量重新获取整个 connection。官方文档usePaginationFragment API 参考当前版本见 use-pagination-fragment.mdx用一个 好友列表 的例子直观展示了其用法。下面我们逐层拆解。基本用法渲染并翻页一个好友列表usePaginationFragment接收一个graphqlfragment 和一个 fragment reference返回分页所需的一切。结合当前文档目录中的最新版示例其中补充了argumentDefinitions声明分页变量一个完整的例子如下import type {FriendsList_user$key} from FriendsList_user.graphql; const React require(React); const {graphql, usePaginationFragment} require(react-relay); type Props { user: FriendsList_user$key, }; function FriendsList(props: Props) { const { data, loadNext, loadPrevious, hasNext, hasPrevious, isLoadingNext, isLoadingPrevious, refetch, // For refetching connection } usePaginationFragment( graphql fragment FriendsListComponent_user on User argumentDefinitions( count: { type: Int, defaultValue: 5 } cursor: { type: String } ) refetchable(queryName: FriendsListPaginationQuery) { name friends(first: $count, after: $cursor) connection(key: FriendsList_user_friends) { edges { node { name age } } } } , props.user, ); return ( h1Friends of {data.name}:/h1 List items{data.friends?.edges.map(edge edge.node)} {node { return ( div {node.name} - {node.age} /div ); }} /List Button onClick{() loadNext(10)}Load more friends/Button / ); } module.exports FriendsList;几个关键点argumentDefinitions声明了 fragment 用到的两个变量count每页数量默认 5和cursor当前光标无默认值它们在 connection 字段friends(first: $count, after: $cursor)上被消费refetchable(queryName: FriendsListPaginationQuery)告诉 Relay 为该 fragment 自动生成一个名为FriendsListPaginationQuery的分页查询——你不需要手写这个查询connection(key: FriendsList_user_friends)标记friends是一个 connection 字段key 用于在 Relay store 中稳定标识这条连接key的规范格式是FragmentName_fieldNameloadNext(10)表示向后加载 10 条data.friends中的数据会随之自动更新并触发重渲染。如果省略argumentDefinitions直接使用$count/$cursor则需要在包含该 fragment 的父查询中传入这些变量旧版文档示例即如此使用argumentDefinitions显式声明可以让 fragment 自带默认值父查询无需再操心分页变量。参数详解fragment 与 fragmentReferenceusePaginationFragment接受两个参数。fragment一个graphql模板字面量定义的 fragment该 fragment 必须满足两个硬性约束否则 Hook 会直接抛错这两个错误都在 usePaginationFragment.js 内部经由getPaginationMetadata触发见 getPaginationMetadata.js 中的 invariant 检查必须包含connection指令connection 字段上没有connection指令时getPaginationMetadata会抛出Expected fragment ... to include a connection when using ... Did you forget to add a connection directive to the connection field in the fragment?必须包含refetchable指令缺失时抛出Did you forget to add a refetchable directive to the fragment?该行为在测试 usePaginationFragment-test.js 中有专门用例覆盖。refetchable指令只能加在可 refetch的 fragment 上即声明在以下类型上的 fragmentViewer类型Query类型实现了Node接口的类型即拥有id字段的类型。需要特别说明的是refetchable会自动生成分页查询你只需指定queryNameRelay 编译器会生成对应查询并同时生成该查询的 Flow 类型可以从生成文件queryName.graphql.js中导入。fragmentReference不透明的 fragment 引用第二个参数是fragment reference——一个不透明的 Relay 对象Relay 用它从 store 中读取该 fragment 的数据更具体地说它包含了数据应该从哪一个具体对象实例上读取的信息fragment owner、变量、data ID 等。fragment reference 的类型可以从生成的 Flow 类型文件fragment_name.graphql.js中导入用于声明组件Props的类型其命名规范为fragment_name$key。官方文档建议配合eslint-plugin-relay的 lint 规则来强制校验 fragment reference prop 的类型声明正确仓库内可参考 eslint-plugin-relay-internal 的实现思路。返回值全解析8 个成员逐一拆解usePaginationFragment返回一个包含以下属性的对象。下面是各属性的官方语义并补充了当前仓库源码层面的实现佐证。data从 Relay store 中读出的数据对象其形状与定义的 fragment 完全一致Flow 类型也据此生成字段类型由 GraphQL Schema 推导。data通过内部 useRefetchableFragmentInternal.js 中的useFragmentInternal读取因此组件会自动订阅该 fragment 的数据变化。hasNext/hasPrevioushasNext布尔值表示在向前方向上是否已经到达连接末尾。若该方向还有更多数据可查则为true否则为false。hasPrevious同上但针对向后方向。它们的计算逻辑在 getConnectionState.jsuseLoadMoreFunction从 fragment 数据中取出 connection 的pageInfo向前方向读取endCursor与hasNextPage向后方向读取startCursor与hasPrevPage。只有当cursor非空且对应的hasNextPage/hasPrevPage为true时才认为还有更多if (direction forward) { hasMore cursor ! null pageInfo[HAS_NEXT_PAGE] true; } else { hasMore cursor ! null pageInfo[HAS_PREV_PAGE] true; }注意connection 为null、edges/pageInfo缺失等异常情况都会被安全地处理为hasMore: false不会崩溃。isLoadingNext/isLoadingPrevious布尔值指示向前 / 向后方向的分页请求当前是否在途包括任何增量数据 payload。这两个状态由 usePaginationFragment.js 内部的useLoadMore管理底层 useLoadMoreFunction.js 通过一个 observer 在请求start时置true在complete/error以及可选的unsubscribe由RelayFeatureFlags.ENABLE_USE_PAGINATION_IS_LOADING_FIX控制时置回falseconst observer { start: () setIsLoadingMore(true), complete: () setIsLoadingMore(false), error: () setIsLoadingMore(false), ... };setIsLoadingMore会优先通过 environment 的 scheduler 调度更新以便与 store 更新在同一批次中被其他组件观察到避免闪烁。loadNext(count, options?)用于在向前方向加载更多数据。参数count必填本次分页请求要查询的条目数量options可选onComplete请求完成含增量 payload时调用若请求出错会以Error对象作为第一个参数调用。返回值disposable包含dispose函数的对象调用disposable.dispose()可取消该分页请求。行为调用loadNext不会让组件 suspend。请求在途时isLoadingNext变为true返回的新条目被追加到 connection 中触发组件重渲染分页请求总是复用最初获取该 connection 时使用的变量只有分页变量count/cursor会变化。改变分页变量之外的任何变量都是没有意义的——那意味着你在查询另一条完全不同的连接。**源码验证**在 useLoadMoreFunction.js 中loadMore会把parentVariables与fragmentVariables合并为baseVariables再调用getPaginationVariables(direction, count, cursor, baseVariables, extraVariables, paginationMetadata)生成最终请求变量并用createOperationDescriptor(..., {force: true})强制执行网络请求即loadNext不走缓存。生成的变量中向前方向会设置forward.cursor/forward.count并将向后方向变量置空const paginationVariables { ...baseVariables, ...extraVariables, [forwardMetadata.cursor]: cursor, [forwardMetadata.count]: count, };见 getPaginationVariables.js该文件同时会校验UNSTABLE_extraVariables不得包含 cursor / count 变量因为它们由 Relay 自动确定。一个重要的内部细节loadNext不会 suspend但如果当前组件已经卸载unmountedloadMore会直接返回一个空disposable并打印警告Unexpected fetch on unmounted component同理若 fragment 数据为空或父查询仍在激活状态调用会被安全地跳过。这与refetch的行为形成对比见下文。loadPrevious(count, options?)与loadNext完全对称方向为向后使用before/last风格的分页变量。参数count、options.onComplete、返回值disposable与行为特性不 suspend、isLoadingPrevious状态、复用原变量与loadNext一致。底层同样是 useLoadMoreFunction.jsusePaginationFragment通过两次调用useLoadMore一次direction: forward、一次direction: backward实现同时双向分页。refetch(variables, options?)用于以一组可能全新的变量重新获取这个 connection fragment。参数variables一组新的变量值用于获取refetchable查询。这些变量需要与 fragment 内引用的 GraphQL 变量匹配。不过只需提供你想要改变的变量fragment 引用的变量中未提供的部分会自动回退到父查询中的原始值。因此想用与最初完全相同的变量重新获取 fragment直接调用refetch({})即可对于$id变量同理除非你想用不同的id重新获取否则可以不传——refetchrefetchablefragment 时 Relay 已经知道当前渲染对象的 id。options可选fetchPolicy决定是否使用缓存数据以及在有缓存时何时发网络请求完整规范见 Fetch Policies 指南onCompleterefetch 请求完成含增量 payload后调用。返回值disposable包含dispose函数调用后可取消 refetch 请求。行为以新变量调用refetch会用新变量重新获取 fragment只需提供 fragment 内引用的变量。官方文档以翻译场景为例通过给lang变量传新值重新获取当前渲染 Comment 的翻译后正文与loadNext/loadPrevious不同refetch会重渲染组件并可能让组件 suspend——具体取决于fetchPolicy以及是否有可用缓存、是否需要等待网络请求。如果 refetch 导致 suspend必须确保该组件外层有Suspense边界包裹。**源码验证**在 useRefetchableFragmentInternal.js 中refetch的变量合并顺序为{...parentVariables, ...fragmentVariables, ...providedRefetchVariables}即未提供的变量回退到原始父查询值正是由这一展开顺序保证的。随后若identifierInfo存在且调用方未显式提供标识符变量如id会从fragmentData中读取identifierField的值自动补上这就是Relay 已经知道当前对象 id的实现通过createOperationDescriptor(refetchableRequest, refetchVariables, {force: true})构造操作并调用loadQuery发起请求之后在渲染阶段useRefetchableFragmentInternal会消费 refetch 查询结果从响应路径fragmentRefPathInResponse提取出新的 fragment ref 并重新读取 fragment若请求仍在途则在此处 suspend此外开发模式下该文件还会校验 refetch 前后返回的id与__typename是否一致checkSameIDAfterRefetch/checkSameTypeAfterRefetch帮助尽早发现服务器端 id 唯一性实现的问题。行为特性订阅、Suspense 与分页的边界官方文档在 Behavior 一节明确了三条关键行为自动订阅数据更新组件自动订阅 fragment 数据的更新。无论更新来自何处例如拉取新数据、或通过 mutation 修改已有数据只要该User的数据发生变化组件都会用最新数据自动重渲染缺失数据时 suspend如果该 fragment 的某些数据缺失且这些数据正被某个父查询获取组件会 suspend。更多细节见 Loading States with Suspense 指南分页不会 suspendloadNext/loadPrevious不会导致组件 suspend与refetch形成鲜明对比。分页请求在途时组件通过isLoadingNext/isLoadingPrevious感知加载状态新条目到达后正常重渲染。与PaginationContainer的差异usePaginationFragment是 hooks 时代的 API与旧版类组件 APIPaginationContainer相比官方文档总结了以下差异不再需要手写分页查询PaginationContainer要求你显式提供分页 queryusePaginationFragment通过refetchablefragment 让 Relay 自动生成分页查询开箱即用支持同时双向分页PaginationContainer一次只能配置一个方向而 hooks 版同时管理loadNext/loadPrevious无需getVariables/getFragmentVariables配置函数这直接消解了旧 API 中variables与fragmentVariables这对定义模糊的概念。分页请求始终复用最初获取 connection 的变量仅分页变量会改变无需direction/getConnectionFromProps配置这些值由 Relay 根据 fragment 自动确定refetch 不再区分variables与fragmentVariablesusePaginationFragment的 refetch 永远正确地以你提供的变量重新获取并渲染 fragment未提供的变量回退到父查询原始值refetch 必定更新组件这在PaginationContainer的refetchConnection中并不总是成立——旧 API 是否更新组件取决于 refetch 查询查了什么、以及 fragment 是否定义在正确的对象类型上。从实现上看两者的差异根植于数据流模型的不同PaginationContainer依赖配置函数把外部变量映射为fragment 变量而 hooks 版通过 getPaginationVariables.js 直接在原始变量的基础上叠加分页变量从根上消除了两套变量概念。源码视角一次loadNext的完整调用链把上面的内容串起来一次loadNext(10)在仓库中的完整调用链如下对应文件均位于当前仓库usePaginationFragment.jsgetPaginationMetadata(fragmentNode, ...)校验connection/refetchable并取出paginationRequest、connectionPathInFragmentData、paginationMetadatauseLoadMoreFunction.jsgetConnectionState(direction, ...)从当前 fragment 数据中读取cursor与hasMoregetConnectionState.js合并parentVariables与fragmentVariables得到baseVariables调用 getPaginationVariables.js 生成带cursor/count的请求变量若 fragment 需要id等标识符自动从fragmentData读取补全createOperationDescriptor(paginationRequest, paginationVariables, {force: true})构造操作描述符通过fetchQuery(environment, ...)发起网络请求observer 驱动isLoadingNext状态切换返回{dispose: disposeFetch}作为disposable新数据写入 store 后data自动更新并触发组件重渲染useFragmentInternal的订阅机制。常见错误排查结合源码中的 invariant / warning 信息实践中常见的问题可以快速定位症状原因与对策调用usePaginationFragment时抛错 Did you forget to add a connection directive...fragment 的 connection 字段缺少connection指令检查key是否按FragmentName_fieldName命名见 getPaginationMetadata.js抛错 Did you forget to add a refetchable directive...fragment 缺少refetchable(queryName: ...)且 fragment 必须定义在Query/Viewer/ 实现了Node的类型上测试用例见 usePaginationFragment-test.js点击加载更多无反应且控制台出现 Unexpected fetch on unmounted component组件已卸载但仍触发了分页检查定时器、事件监听等异步来源是否在卸载后清理useLoadMoreFunction.jshasNext一直是false确认服务器正确返回了pageInfo.hasNextPage以及向后分页时的hasPreviousPagegetConnectionState依赖这两个字段判断getConnectionState.jsrefetch导致组件白屏refetch可能 suspend检查组件外层是否有Suspense边界并确认fetchPolicy符合预期Loading States with Suspense 指南小结usePaginationFragment把 Relay 连接分页的复杂度收敛到了一个 Hookrefetchableconnection声明驱动编译器自动生成分页查询loadNext/loadPrevious提供不 suspend 的双向翻页refetch提供可配置 fetchPolicy 的重新获取而hasNext/hasPrevious/isLoadingNext/isLoadingPrevious则让 UI 状态与请求生命周期精确同步。相比PaginationContainer它不再需要手写分页查询与配置函数变量模型也更简单一致。理解其背后的数据流store 订阅、连接状态读取、变量合并与标识符补全将帮助你在遇到边界情况时快速定位问题。【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relay创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

2026最新会议拍摄避坑指南:3个致命错误让你返工到哭
2026最新会议拍摄避坑指南:3个致命错误让你返工到哭

2026最新会议拍摄避坑指南:3个致命错误让你返工到哭 面试被问“为什么视频发出去客户说画质渣”或者“为什么录音全是风声”,你答不上来?别慌,这不仅是技术问题,更是流程问题。2026年最新的工作流里,很多老手还在用2020年的思路拍会议,结… · 2026/9/23 1:45:23

Cosmos 中基于节点度数(Degree of Nodes)检测无向图环的 C++ 实现指南
Cosmos 中基于节点度数(Degree of Nodes)检测无向图环的 C++ 实现指南

Cosmos 中基于节点度数(Degree of Nodes)检测无向图环的 C 实现指南 【免费下载链接】cosmos Worlds largest Contributor driven code dataset | Used in Quark Search Engine, OpenGenus IQ, OpenGenus Visual Project 项目地址: https://gitcode.co… · 2026/9/23 1:45:17

3步搞定try组合图解原理,拒绝代码跑不通
3步搞定try组合图解原理,拒绝代码跑不通

3步搞定try组合图解原理,拒绝代码跑不通 复制来的代码跑不通不知道怎么调?别急,咱们用图解原理把底层逻辑拆明白。很多应届生拿到开源项目,一运行就报错,其实90%的问题出在对 try… · 2026/9/23 1:45:11

iOS H5混合应用IPA包资源与配置文件混淆加固实战指南
iOS H5混合应用IPA包资源与配置文件混淆加固实战指南

搞 iOS 混合应用开发的朋友,应该都遇到过这种情况:辛辛苦苦写好的 H5 页面、接口配置、业务逻辑,打包成 IPA 之后,总担心被别人拿去做“研究”。尤其是现在很多 App 的核心业务都跑在 WKWebView 里,H5 资源和配置文件基… · 2026/9/23 2:40:58

情感陪伴的价值与高质量互动实践
情感陪伴的价值与高质量互动实践

1. 情感陪伴的价值与意义现代社会中,人与人之间的情感连接正在变得愈发珍贵。在快节奏的生活压力下,那些看似平凡的日常互动——家人围坐的晚餐时光、朋友间的深夜畅谈、伴侣间的默契陪伴,往往成为支撑我们继续前行的精神力量。心理学研究表明… · 2026/9/23 2:40:58

3步解决u盘在电脑上读不出来,最佳实践避坑指南
3步解决u盘在电脑上读不出来,最佳实践避坑指南

3步解决u盘在电脑上读不出来,最佳实践避坑指南 面试被问原理答不上来?别慌,u盘在电脑上读不出来这种“小毛病”,往往藏着设备管理的大坑。很多开发者以为只是硬件坏了,其实90%是系统驱动、权限或文件系统配置问题。掌握最佳实践,不仅能快速修复现… · 2026/9/23 2:40:58

高密度计算集群散热技术解析与实战
高密度计算集群散热技术解析与实战

1. 项目概述:ClawdBOT现象与算力需求激增最近科技圈被一个叫ClawdBOT的项目刷屏了。这个看似普通的分布式计算平台,在短短三个月内用户量暴涨300倍,服务器集群规模从最初的200节点扩张到现在的6万节点。作为参与过多个大型计算项目部署的老兵… · 2026/9/23 2:40:52

FFmpeg -22错误码全解析:从Invalid argument到排查实战
FFmpeg -22错误码全解析:从Invalid argument到排查实战

1. 认识 -22:这个神秘数字到底是什么先说结论:FFmpeg 的 -22 错误码,本质上是系统调用返回的EINVAL(Invalid argument),翻译成人话就是“参数不合法”。很多入坑 FFmpeg 的人第一次看到这个报错&#xff0c… · 2026/9/23 2:40:52

产品设计AI工具选型指南:从场景拆解到七款工具能力边界
产品设计AI工具选型指南:从场景拆解到七款工具能力边界

1. 产品设计AI工具选型的底层逻辑1.1 为什么“哪个AI工具最好用”是个伪命题每年年初我都会被同行问同一个问题:“2026年了,做产品设计到底该用哪款AI工具?”问的人里,有刚入行的交互设计师,有带团队的产品负责人&… · 2026/9/23 2:40:52

3招搞定手机怎么下载微信面试难题实战项目解析
3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧
Win7无线热点配置工具源码解析:解决API失效的3个实战技巧

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧 Win7无线热点配置工具在Win10/11上跑不动?不是你的问题,是版本升级后 API 全变了。很多老项目里的 netsh wlan… · 2026/9/23 0:00:36

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码