资讯动态

Relay 17 连接(Connection)重取与筛选:使用 usePaginationFragment 的 refetch 实现过滤与排序

发布时间:2026/9/23 4:45:37 来源:尧图企业网站定制
前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载Relay 17 连接Connection重取与筛选使用 usePaginationFragment 的 refetch 实现过滤与排序导读本指南聚焦 Relay 中“带筛选条件的连接数据重取refetching connections”这一核心场景。当你的列表需要根据搜索词、排序方式等参数改变查询结果时如何既能保留分页能力、又能在参数变化时重新获取数据本文以usePaginationFragment的refetch函数为主线讲解筛选参数的传递、分页时参数的保留机制、refetch 的触发时机与变量合并规则并结合react-relay与relay-runtime的源码usePaginationFragment.js、useRefetchableFragmentInternal.js揭示底层实现。读完你将掌握在 Relay 17 中实现搜索型列表、排序切换等实战方案。在 GraphQL 中连接Connection字段往往不只是返回一个固定列表而是可以接收参数用来筛选filter结果集或改变排序方式sort。典型场景包括搜索 typeahead自动补全结果列表由用户输入的搜索词动态过滤切换评论排序模式对某篇文章的评论用户可选择不同的排序方式服务器返回完全不同的评论集合改变信息流News Feed的排序方式。本文要解决的问题正是当这些“筛选参数”变化时如何让已渲染的连接数据重新获取refetch同时不影响已有的分页pagination能力。本文基于仓库中 refetching-connections.md 编写并结合当前仓库中 Relay 17 版本的源码进行深入验证。一、连接字段如何接收筛选参数在 GraphQL 层面连接字段可以接收任意参数来筛选或排序结果。例如下面的 fragment 请求了一个按order_by排序、按search_term过滤的好友列表fragment UserFragment on User { name friends(order_by: DATE_ADDED, search_term: Alice, first: 10) { edges { node { name age } } } }其中order_by、search_term是筛选/排序参数而first是分页参数表示取前 10 条。first、after、before、last这类分页参数由 Relay 自动管理但筛选参数则由业务逻辑控制。在 Relay 中我们通常不把筛选参数写死成字面量而是通过 GraphQL 变量variables传入这样运行时才能动态改变它们。使用usePaginationFragment时fragment 中可以这样写type Props { userRef: FriendsListComponent_user$key, }; function FriendsListComponent(props: Props) { const userRef props.userRef; const {data, ...} usePaginationFragment( graphql fragment FriendsListComponent_user on User { name friends( order_by: $orderBy, search_term: $searchTerm, after: $cursor, first: $count, ) connection(key: FriendsListComponent_user_friends_connection) { edges { node { name age } } } } , userRef, ); return (...); }这里有几个关键点$orderBy、$searchTerm是筛选变量$cursor、$count是分页变量connection(key: FriendsListComponent_user_friends_connection)指令告诉 Relay 这是一个需要规范化的连接字段key用于在 Relay store 中唯一标识该连接详见 transform_connections.rs 中build_connection_metadata等实现使用usePaginationFragment的前提是 fragment 同时被标注refetchable(queryName: ...)编译器会为该 fragment 自动生成一个分页查询可参考 pagination.md 中的完整写法。二、分页时筛选参数会被保留usePaginationFragment返回的loadNext(count)用于加载下一页。一个容易忽视但至关重要的行为是调用loadNext时会继续使用初始查询时的筛选参数值。type Props { userRef: FriendsListComponent_user$key, }; function FriendsListComponent(props: Props) { const {data, loadNext} usePaginationFragment( graphql fragment FriendsListComponent_user on User { name friends(order_by: $orderBy, search_term: $searchTerm) connection(key: FriendsListComponent_user_friends_connection) { edges { node { name age } } } } , userRef, ); return ( h1Friends of {data.name}:/h1 List items{data.friends?.nodes}{...}/List {/* Loading the next items will use the original order_by and search_term values used for the initial query */} Button onClick{() loadNext(10)}Load more friends/Button / ); }注意调用loadNext会沿用初始查询时的order_by和search_term值。在分页过程中这些值不会也不应该改变——因为后续页面必须与第一页处于同一筛选条件下分页才有意义。从源码看这一行为由 useLoadMoreFunction.js 中的loadMore回调实现它从 fragment selector 中取出parentVariables和fragmentVariables合并为baseVariables再调用getPaginationVariables生成分页请求的变量const parentVariables fragmentSelector.owner.variables; const fragmentVariables fragmentSelector.variables; const extraVariables options?.UNSTABLE_extraVariables; const baseVariables { ...parentVariables, ...fragmentVariables, }; const paginationVariables getPaginationVariables( direction, count, cursor, baseVariables, {...extraVariables}, paginationMetadata, );而 getPaginationVariables.js 只会覆盖cursor和count这两个分页变量其余变量包括orderBy、searchTerm原样保留在baseVariables中从而实现“分页沿用原筛选条件”的效果。同时它会将反向分页的游标/数量变量置为null确保 forward/backward 两种方向互不干扰const paginationVariables { ...baseVariables, ...extraVariables, [forwardMetadata.cursor]: cursor, [forwardMetadata.count]: count, }; if (backwardMetadata backwardMetadata.cursor) { paginationVariables[backwardMetadata.cursor] null; } if (backwardMetadata backwardMetadata.count) { paginationVariables[backwardMetadata.count] null; }需要临时在分页请求上附加额外变量时可以使用loadNext(count, {UNSTABLE_extraVariables: {...}})但注意不能把cursor、count放进extraVariables——源码中对此有显式warning见 getPaginationVariables.js。三、用 refetch 以不同的参数重新获取连接如果我们需要用不同的变量重新获取连接——比如用户改变了搜索词或排序方式——就不能再用loadNext而应使用usePaginationFragment提供的refetch函数。其用法与 Refetching Fragments with Different Data 一节中介绍的useRefetchableFragment的refetch完全一致。3.1 完整示例搜索词变化时自动重取/** * FriendsListComponent.react.js */ import type {FriendsListComponent_user$key} from FriendsListComponent_user.graphql; const React require(React); const {useState, useEffect} require(React); const {graphql, usePaginationFragment} require(react-relay); type Props { searchTerm?: string, user: FriendsListComponent_user$key, }; function FriendsListComponent(props: Props) { const searchTerm props.searchTerm; const {data, loadNext, refetch} usePaginationFragment( graphql fragment FriendsListComponent_user on User { name friends( order_by: $orderBy, search_term: $searchTerm, after: $cursor, first: $count, ) connection(key: FriendsListComponent_user_friends_connection) { edges { node { name age } } } } , props.user, ); useEffect(() { // When the searchTerm provided via props changes, refetch the connection // with the new searchTerm refetch({first: 10, search_term: searchTerm}, {fetchPolicy: store-or-network}); }, [searchTerm]) return ( h1Friends of {data.name}:/h1 {/* When the button is clicked, refetch the connection but sorted differently */} Button onClick{() refetch({first: 10, orderBy: DATE_ADDED}); } Sort by date added /Button List items{data.friends?.nodes}.../List Button onClick{() loadNext(10)}Load more friends/Button / ); }3.2 提炼关键行为逐条解读上面这个例子refetch接受新变量集合并重新抓取 fragment。你传入的变量是生成的查询所期望变量的一个子集生成的查询需要id当 fragment 类型带有id字段时以及 fragment 中传递引用的所有其他变量。在本例中我们必须传first要抓取的条数并可传入不同的筛选值如orderBy、searchTerm。注意这里refetch({first: 10, order_by: searchTerm})的写法来自文档示例实际项目中变量名以你在 fragment 中声明的$orderBy/$searchTerm为准。可能触发 Suspenserefetch会重新渲染组件如果需要发起并等待网络请求组件可能进入 suspend 状态详见 Loading States with Suspense。因此必须确保组件上层有Suspense边界来展示 fallback。refetch 是“从零开始”调用refetch在概念上等于从头重新抓取连接并重置分页状态。例如用不同的search_term抓取后之前search_term的分页信息不再有意义——我们实际上是在对一个全新列表进行分页。3.3 fetchPolicy 的选择示例中使用了fetchPolicy: store-or-network。Relay 17 中合法的fetchPolicy取值在 RelayRuntimeTypes.js 中定义为fetchPolicy行为store-or-network若 store 中已有满足条件的数据则直接复用跳过网络请求否则发起网络请求store-and-network先读 store 数据立即渲染同时发起网络请求请求完成后用最新数据更新network-only始终发起网络请求不读缓存store-only只读 store绝不发起网络请求若希望避免 Suspense可先用fetchQuery把数据写入 store再以fetchPolicy: store-only调用refetch此时数据已缓存、不会 suspend完整做法见 refetching-fragments-with-different-data.md 的 If you need to avoid Suspense 小节。refetch还支持UNSTABLE_renderPolicy: full | partial对应 RelayRuntimeTypes.js 的RenderPolicy与onComplete回调类型定义见 useRefetchableFragmentInternal.js。四、源码视角refetch 与分页是如何协同的usePaginationFragment本质上是useRefetchableFragmentInternal负责refetch与useLoadMoreFunction负责loadNext/loadPrevious的组合。查看 usePaginationFragment.js 可以看到通过getPaginationMetadata从 fragment 中解析连接路径与分页元数据通过useRefetchableFragmentInternal获得refetch能力分别构造 forward / backward 两个方向的loadNext/loadPrevious关键点refetchPagination在真正调用refetch之前会先disposeFetchNext()和disposeFetchPrevious()——即取消所有在途的分页请求const refetchPagination useCallback( (variables: TVariables, options: void | Options) { disposeFetchNext(); disposeFetchPrevious(); return refetch(variables, {...options, __environment: undefined}); }, [disposeFetchNext, disposeFetchPrevious, refetch], );这从源码层面印证了文档中的结论refetch 会重置分页状态——旧的分页请求被主动作废连接从第一条开始重新获取。再看 useRefetchableFragmentInternal.js 中refetch的实现变量合并顺序为const refetchVariables: VariablesOfTQuery { ...(parentVariables as $FlowFixMe), ...fragmentVariables, ...providedRefetchVariables, };即未在refetch调用中提供的变量会从原 fragment owner 的变量中继承。因此你不需要每次把所有变量都传齐只需要传“发生变化的”以及必要的firstcount即可。同时如果生成的查询需要标识符如id且未显式传入Relay 会从 fragment data 中读取并自动填充if ( identifierInfo ! null !providedRefetchVariables.hasOwnProperty( identifierInfo.identifierQueryVariableName, ) ) { refetchVariables[identifierInfo.identifierQueryVariableName] identifierValue; }相关运行时期类型与getRefetchMetadata、getPaginationMetadata的返回结构可参考 getPaginationMetadata.js 与relay-runtime的 index.d.ts。connection指令在编译器侧的处理连接元数据构建、handle field 生成位于 transform_connections.rs。五、实践清单与常见陷阱综合文档与源码给出可复制的实践要点fragment 需要refetchableusePaginationFragment要求 fragment 可 refetch作用于Viewer、Query、实现Node的类型或fetchable类型并配合connection(key: ...)标注连接字段。筛选变量通过 GraphQL 变量传入不要把筛选条件写死成字面量否则无法在运行时改变它们。分页沿用原筛选条件loadNext/loadPrevious会复用初始查询的筛选变量不要在分页时试图改变筛选条件。改变筛选条件用refetch传入新变量必要时含firstRelay 会自动继承未变化的变量、填充id并取消在途分页请求、重置分页游标。务必有Suspense边界refetch可能触发 suspend组件上层需要Suspense包裹想避免 Suspense 时使用fetchQueryfetchPolicy: store-only的组合。变量命名一致性GraphQL 变量名$orderBy/$searchTerm/$cursor/$count必须在 fragment 中声明并在查询根refetchable生成查询中可用生成的查询会要求id若类型有id字段以及 fragment 中传递引用的所有其他变量。延伸阅读分页入门与loadNext/hasNext用法pagination.md连接Connection概念与游标分页connections.md通用 fragment 重取useRefetchableFragmentrefetching-fragments-with-different-data.md高级分页自定义 append/prepend 行为advanced-pagination.md运行时实现usePaginationFragment.js、useRefetchableFragmentInternal.js、getPaginationVariables.js赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐OpenHermes-2.5-neural-chat-7b-v3-1-7B深度解析Mistral架构融合模型如何革新AI对话体验OpenHermes 2.5 neural chat 7b v3 1 7B深度解析Mistral架构融合模型如何革新AI对话体验 想要体验 Mistral架构前端开发工具Relay 连接Connection重取指南使用与变更筛选条件usePaginationFragment 的 refetch 实战解析Relay 连接Connection重取指南使用与变更筛选条件usePaginationFragment 的 refetch 实战解析 在 Relay前端开发工具Relay 连接数据重新获取Refetching Connections使用 usePaginationFragment 实现过滤与排序变量切换Relay 连接数据重新获取Refetching Connections使用 usePaginationFragment 实现过滤与排序变量切换 导读 在前端开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价