【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载本文基于 HowToGraphQLThe Fullstack Tutorial for GraphQL仓库中 React Relay 教程的分页章节 整理。你将跟随该章节完成一个完整的实战目标在教程的 Hackernews 克隆应用React Relay Moderncreate-react-app构建Graphcool 提供后端中把一次加载全部链接的列表改造成底部带 “More” 按钮、按块追加加载的游标分页列表。读完并动手完成后你将掌握 Relay Connection 规范Edge/Node/cursor/pageInfo、createPaginationContainer的完整配置对象、根查询变量透传以及relay.loadMore / hasMore / isLoading的守卫式调用方式。一、教程上下文本章在做什么该章节位于整条教程线的收尾阶段。在此之前应用已经具备了链接列表展示第 2 章使用createFragmentContainerQueryRenderer、创建链接与投票的 Mutation第 3、6 章、登录认证第 5 章、路由第 4 章和订阅实时刷新第 7 章。分页一章的目标非常具体让用户以更小、更可控的块来加载链接而不是一次性拉取全部。实现形式是列表底部放一个More按钮每点一次就向后端再取一块数据追加到现有列表后面。教程目录规划文件 meta/structure/react-relay.md 中Pagination 一章规划的内容正是 “Limit/Offset vs Cursor、Relay Connections、Load Chunks of Links”与本文件的正文完全对应也说明了本章的侧重点用游标分页替代传统的 limit-offset“numbered pages”分页。一个值得注意的前置事实由于本章要求服务端严格遵循 Relay Connection 规范教程采用的后端是 Graphcool 的Relay API端点https://api.graph.cool/relay/v1/project-id形式见 Getting Started 章节并且schema.graphql是完整下载后、针对当时 relay-compiler 的已知 bug 手工调整过的版本。这些环境细节决定了本套代码的适用前提。另外HowToGraphQL 站点本身用 Gatsby React 渲染这些教程正文中所有Instruction教学块由 Instruction 组件 负责渲染成高亮样式它会筛选出P/PRE子节点分别套用instruction-block、instruction-code样式。阅读线上版本时看到的高亮块其实现就是这段源码。二、Relay Connections分页的数据结构基础在 Relay 中列表不是普通的数组而是Connection连接抽象。它的目的是给一份简单的元素列表“增补”元信息——关于列表本身的信息让客户端可以基于游标实现比 limit-offset 更稳的分页。这也解释了为什么在本教程的早期章节里每次访问列表元素都要做edges-node这一套“舞蹈”Connection 不直接暴露元素而是把每个元素包在Edge里Edge携带该元素在列表中的上下文位置以及它前后相邻的部分。按 Relay Cursor Connections Specification一个合格的 Connection服务端需要满足每个元素被包进Edge类型Edge至少暴露两个字段node真正的元素数据cursor该元素在列表中的位置。注意cursor是一个不透明opaque字符串——它不能也不应该在客户端自行生成只能原样回传给服务端。Connection 自身暴露pageInfo字段其中包含四个子字段hasNextPage布尔值是否已到列表末尾向前翻页时才有意义hasPreviousPage布尔值是否已到列表开头向后翻页时才有意义startCursor/endCursor本次返回的 edges 中第一条、最后一条对应的游标。Connection 支持用于切片/分页的实参first和last整型用来切片、只取列表的一个子集before和after字符串接收游标指定切片从哪里开始。此外教程的后端 Graphcool 还在 Connection 上额外实现了count字段用于直接查询当前列表中的元素总数。最后一条实参正是分页能力的来源firstafter组合构成向前分页forward paginationlastbefore组合构成向后分页backward pagination二者都能精确取出列表中的一个具体块。而且 Relay 要求从 Connection 取数据时必须带上至少first或last之一before/after可选——Relay Compiler 会在编译期检查缺失时直接报错。官方规范全文见文档引用的 Relay Cursor Connections Specification文档中给出的相关讨论Relay 仓库 issue #1201、graphql-relay-js issue #20围绕的正是这一强制要求。与第 2 章的衔接当时的片段里allLinks(last: 100, ...)其实已经是合法的 Connection 取法last满足“必须有 first 或 last”只是写死了 100 条且没有after。本章把last: 100换成变量化的first: $countafter: $after并保留同一个connection(key: LinkList_allLinks)——key 不变意味着 Relay Store 里的是同一条连接新取到的块才能被正确追加而不是当成另一份数据。三、PaginationContainerAPIFragmentContainercreateFragmentContainer你已经用过组件 一个声明数据依赖的 GraphQL fragmentRelay 负责决定何时、如何取数。本章引入的是它的“分页增强版”——PaginationContainer当数据来自 Connection 时用它代替FragmentContainer它会直接内置分页所需的便利方法。PaginationContainer的前提同样是服务端必须严格遵循 Connection 规范因为整个实现依赖上面那些字段真实存在。注入到组件 props 的relay对象上提供四个方法原文档列出的完整清单方法返回作用hasMoreboolean是否至少还有下一页可加载isLoadingboolean由loadMore触发的一或多个请求是否仍在进行中loadMore-加载当前连接的下一块数据分页方向forward/backward由容器配置推断refetchConnection-重新抓取连接中的数据可带新的 variables这四个方法就是本教程整个_loadMore逻辑的全部依赖。四、实现步骤一为LinkList准备 “More” 按钮和其他章节一样先把纯 React 组件调好数据获取逻辑后补。这里只需在LinkList组件底部加一个More按钮并预留_loadMore方法桩。LinkList.js的render调整为高亮行为本章新增部分render() { return ( div div {this.props.viewer.allLinks.edges.map(({node}, index) ( Link key{node.__id} index{index} link{node}/ ))} /div div classNameflex ml4 mv3 gray div classNamepointer onClick{() this._loadMore()}More/div /div /div ) }注意两点细节this.props.viewer.allLinks.edges.map(...)仍是 Connection 的edges - node访问模式node通过__idRelay 注入的内部唯一 id作为 Reactkey按钮绑定onClick{() this._loadMore()}样式类来自教程在 Getting Started 章节 中引入的 Tachyonspointer/flex/ml4/mv3/gray。然后在类中补上方法桩_loadMore() { // ... youll implement this in a bit }五、实现步骤二用createPaginationContainer替换导出这是本章的核心改动。打开LinkList.js把原本的export default createFragmentContainer(...)整体替换为向createPaginationContainer传入组件 fragment 配置 分页配置两部分export default createPaginationContainer(LinkList, { viewer: graphql fragment LinkList_viewer on Viewer { allLinks( first: $count, after: $after, orderBy: createdAt_DESC ) connection(key: LinkList_allLinks) { edges { node { ...Link_link } } pageInfo { hasNextPage endCursor } } } , }, { // ... this will be added soon } )逐点解读这份 fragmentfirst: $count取代了原来硬编码的last: 100。之所以用first是因为要做的就是向前分页从列表开头向末尾一块块推进如果要做向后分页从末尾往前取则要改用last并在pageInfo里请求hasPreviousPage和startCursor。after: $after接收游标指示列表从哪个位置开始切片。orderBy: createdAt_DESC保持按创建时间倒序保证块与块之间的顺序稳定。connection(key: LinkList_allLinks)与第 2 章相同的连接 key这是 Relay 在 Store 中定位、更新该连接所必需的。pageInfo { hasNextPage endCursor }向前分页只需要这两个字段。hasNextPage供hasMore判断endCursor会被自动用于下一次请求的after。若实现向后分页则对应请求hasPreviousPage与startCursor。fragment 命名遵循 Relay 约定FileName_propName文件是LinkList注入的 prop 是viewer所以是LinkList_viewernode里复用子组件的...Link_link片段因为 Relay 要求父容器的 fragment 包含其子容器的全部数据依赖。六、实现步骤三第二个配置对象分页行为本身把占位的{ ... this will be added soon }替换为完整配置{ direction: forward, query: graphql query LinkListForwardQuery( $count: Int!, $after: String, ) { viewer { ...LinkList_viewer } } , getConnectionFromProps(props) { return props.viewer props.viewer.allLinks }, getFragmentVariables(previousVariables, totalCount) { return { ...previousVariables, count: totalCount, } }, getVariables(props, paginationInfo, fragmentVariables) { return { count: paginationInfo.count, after: paginationInfo.cursor, } }, }各属性含义原文档给出的完整说明并补充适用细节direction声明分页方向取值只有forward或backward两种必须与 fragment 中用的first/last一致本例是forward。query一条独立的查询专门用于loadMore触发的所有请求首次加载走的是QueryRenderer的根查询二者互不干扰。注意它的 variables 与 fragment 一致$count: Int!必填、$after: String可空首页之后才有值。getConnectionFromProps返回你要分页的那个连接。如果组件同时请求多个连接就是靠它区分到底翻哪一个。getFragmentVariables返回读取 fragment 数据所用的变量。签名是(previousVariables, totalCount)这里把累计条数写进countRelay 据此从 Store 中读出连接上已累积的元素。getVariables返回发送分页 query所用的变量。签名是(props, paginationInfo, fragmentVariables)其中paginationInfo.count是loadMore调用时传入的块大小paginationInfo.cursor是 Relay 从pageInfo.endCursor中自动取出的下一块起点。原文档特别提示这份配置对象的官方文档很少可靠的信息来源之一是react-relay包中ReactRelayPaginationContainer.js实现里的注释建议结合实现源码阅读。同时react-relay的导入要同步调整把createFragmentContainer换成createPaginationContainergraphql的导入不变import { createPaginationContainer, graphql } from react-relay七、实现步骤四根查询与QueryRenderer接收变量因为$count是必填变量getVariables/fragment 里用到的变量必须能一路传到组件树根部QueryRenderer的根查询。打开LinkListPage.js让根查询声明这两个变量const LinkListPageQuery graphql query LinkListPageQuery( $count: Int!, $after: String ) { viewer { ...LinkList_viewer } } 再给QueryRenderer补上variablesprop为首次请求提供countQueryRenderer environment{environment} query{LinkListPageQuery} variables{{ count: ITEMS_PER_PAGE, }} render{({error, props}) { if (error) { return div{error.message}/div } else if (props) { return LinkList viewer{props.viewer} / } return divLoading/div }} /这里的environment是教程在 Getting Started 章节 中创建的 Relay Environmentrelay-runtime的EnvironmentNetworkStoreNetwork通过fetch指向 Graphcool 的 Relay API 端点。count指向一个应放在常量声明处的常量。在constants.js第 5 章认证功能中已创建存放GC_USER_ID、GC_AUTH_TOKEN等 key中追加export const ITEMS_PER_PAGE 1 // setting this only to one so you can easily test your pagination implementation设为1是刻意为之每页一条点击 More 就能逐条追加分页行为一目了然。实际项目中这里会是一个常规页大小。然后回到LinkListPage.js导入它import {ITEMS_PER_PAGE} from ../constants八、实现步骤五实现_loadMore并完成编译最后一步把第四章预留的桩方法填上。这是整条教程中_loadMore的最终形态_loadMore() { if (!this.props.relay.hasMore()) { console.log(Nothing more to load) return } else if (this.props.relay.isLoading()) { console.log(Request is already pending) return } this.props.relay.loadMore(ITEMS_PER_PAGE) }三个要点hasMore()守卫pageInfo.hasNextPage为 false 时直接返回避免发出无意义的请求——这直接消费了第二章 Connection 规范里的pageInfo元信息isLoading()守卫上一次loadMore的响应未返回时短路防止并发追加导致连接状态错乱loadMore(ITEMS_PER_PAGE)入参就是块大小Relay 内部用它填充getVariables里的paginationInfo.count并自动从endCursor取出after。别忘了在LinkList.js顶部补上同一句导入import {ITEMS_PER_PAGE} from ../constants到这里代码改动全部完成。由于新增了graphql标签代码fragment 与LinkListForwardQuery必须再跑一次 Relay Compiler 重新生成__generated__产物relay-compiler --src ./src --schema ./schema.graphql在hackernews-react-relay项目根目录执行编译器会把生成物写入./src/__generated__输出形如LinkList_viewer.graphql.js、LinkListPageQuery.graphql.js。随后yarn start启动应用列表初始只加载一条链接底部 More 按钮每点一次追加一条直到hasMore返回 false 后点击只会在控制台打印 “Nothing more to load”。九、要点回顾Connection 是 Relay 分页的地基Edgenode 不透明cursor、pageInfohasNextPage/hasPreviousPage/startCursor/endCursor、以及first/lastbefore/after实参缺一不可且必须至少提供first或last否则 Relay Compiler 拒绝编译。firstafter 向前分页对应pageInfo里的hasNextPage/endCursor向后分页则对称地使用lastbefore与hasPreviousPage/startCursor。createPaginationContainer 组件 fragment 配置 分页配置分页配置的五个关键项是direction、query仅供loadMore使用、getConnectionFromProps、getFragmentVariables、getVariables。connection的 key 要保持稳定本例LinkList_allLinks它决定新加载的块在 Relay Store 中如何与已有数据合并。_loadMore的标准写法是双重守卫 loadMore(块大小)先hasMore()再isLoading()最后relay.loadMore(ITEMS_PER_PAGE)。改动任何graphql标签代码后都要重跑relay-compiler --src ./src --schema ./schema.graphql再启动应用。适用前提说明本套代码复现自 HowToGraphQL 的 ReactRelay 教程线依赖其特定环境——Relay Modernreact-relay 1.x 时代的 API、Graphcool 的 Relay API 端点以及为该环境准备过的schema.graphql。若在当前版本的 Relay 生态中落地API 形态如createPaginationContainer的写法与connection用法可能有差异但 Connection 规范本身与本文讲的结构、分页参数first/after、pageInfo仍然成立。本篇对应原始文档content/frontend/react-relay/8-pagination.md可结合同目录的 Queries 章节、路由章节 与 总结 以及 教程结构规划 一起阅读。赞分享【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载相关推荐howtographql React Relay 教程用 Relay 指令式 API 实现投票 Mutation 与 Store 缓存更新howtographql React Relay 教程用 Relay 指令式 API 实现投票 Mutation 与 Store 缓存更新 本文基于 HoHowToGraphQL ReactRelay 实战用 Relay 与 Graphcool 实现邮箱密码认证AuthenticationHowToGraphQL ReactRelay 实战用 Relay 与 Graphcool 实现邮箱密码认证Authentication 本篇是 HowHowToGraphQL React 教程实战用 Relay Modern 与 GraphQL Subscriptions 实现实时投票数更新HowToGraphQL React 教程实战用 Relay Modern 与 GraphQL Subscriptions 实现实时投票数更新 本文基于 Ho上一篇CANN ops-nn ForeachDivScalar 算子深度解析基于 Ascend C 的 TensorList 标量除法实现与实战下一篇3步搞定视频封面与标签Seal元数据处理全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考