
前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载Relay 作为一个数据驱动的 React 数据层框架会对其所连接的 GraphQL 服务端做出明确且有限的假设。本文以 Relay 官方文档《GraphQL Server Specification》为骨架围绕对象重取Object Identification与连接分页Connections两大核心假设展开通过完整的 Star Wars 示例 Schema、可复现的查询与响应结合本仓库中的源码实现Connections 规范、Object Identification 规范、运行时连接处理与编译期配置帮助读者掌握如何构建一个 Relay 兼容的 GraphQL 服务端并理解 Relay 客户端是如何依赖这些契约完成缓存与分页的。前言Relay 对服务端的两条核心假设Relay 不要求服务端为其定制任何专属能力它对 GraphQL 服务端的全部核心假设可以归结为两点提供一种重取refetch对象的机制—— 即可以通过一个全局唯一的 ID 重新获取任意对象提供一种描述如何在连接connection上翻页的机制—— 即如何对一对多关系进行切片与分页。本文的示例围绕查询《星球大战》原作三部曲中的派系Faction与飞船Ship信息这一场景展开。示例并非对规范的穷尽式描述而是用最短的路径快速引入这两条核心假设为深入阅读规范细节提供上下文。Schema示例用到的完整 GraphQL Schema下面这份 Schema 用于演示一个 Relay 使用的 GraphQL 服务端应当实现的功能。两个核心类型是星战宇宙中的派系Faction和飞船Ship其中每个派系关联多艘飞船interface Node { id: ID! } type Faction implements Node { id: ID! name: String ships: ShipConnection } type Ship implements Node { id: ID! name: String } type ShipConnection { edges: [ShipEdge] pageInfo: PageInfo! } type ShipEdge { cursor: String! node: Ship } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String } type Query { rebels: Faction empire: Faction node(id: ID!): Node }注意其中的关键结构Node接口、node根字段、ShipConnection连接类型、ShipEdge边类型与PageInfo。这四个构件分别对应全局对象标识与游标连接两大规范也是 Relay 客户端能够完成重取与分页的契约基础。Object Identification通过Node接口与node字段重取对象Faction与Ship都带有可用于重取的标识符。Relay 通过Node接口与根查询类型上的node字段来暴露这一能力Node接口只包含一个字段id类型为ID!node根字段接收一个ID!参数返回一个Node。两者协同工作完成重取把查询某个对象时返回的id原样传给node字段就能重新拿到该对象。实战演示查询并重取 Rebels首先查询 Rebels 的 IDquery RebelsQuery { rebels { id name } }返回{ rebels: { id: RmFjdGlvbjox, name: Alliance to Restore the Republic } }现在我们知道了 Rebels 在系统中的 ID就可以重取它query RebelsRefetchQuery { node(id: RmFjdGlvbjox) { id ... on Faction { name } } }返回{ node: { id: RmFjdGlvbjox, name: Alliance to Restore the Republic } }对 Empire 做同样的操作会得到不同的 ID并且同样可以重取query EmpireQuery { empire { id name } }返回{ empire: { id: RmFjdGlvbjox, name: Galactic Empire } }query EmpireRefetchQuery { node(id: RmFjdGlvbjoy) { id ... on Faction { name } } }返回{ node: { id: RmFjdGlvbjoy, name: Galactic Empire } }全局唯一 ID 的合成约定Node接口与node字段的重取机制假设 ID 是全局唯一的。系统本身没有全局唯一 ID 时通常可以通过类型 类型内 ID的组合方式来合成——本示例正是这样做的RmFjdGlvbjox解码后即Faction:1。示例中返回的 ID 是 base64 字符串。ID 被设计为**不透明opaque**的唯一应该传给node的id参数的就是从系统内某个对象上查询id得到的、未经任何改动的原始结果。在 GraphQL 中对字符串做 base64 编码是一种实用惯例用来提醒调用方这是一个不透明标识符不要解析它的内部结构。规范层面的正式要求本仓库 ObjectIdentification.md 给出了正式规范核心要求包括保留类型符合规范的服务端必须保留Node接口与根查询类型上的node字段Node接口必须恰好包含一个名为id、返回非空ID的字段给定该id服务端应能重取对象node根字段必须恰好接收一个名为id的非空 ID 参数返回Node接口当把某个对象id字段的返回值传给node时应当重取到同一个对象尽力而为服务端应尽力取回数据但不保证总能成功例如用户已删除或数据库不可用此时该字段应返回null字段稳定性Field stability如果一次查询中出现两个实现了Node且 ID 相同的对象那么它们必须是相等的——对两个对象查询同一字段必须得到相同结果标量按标量相等、对象按此规则递归定义复数标识根字段Plural identifying root fields服务端还可以暴露接收列表参数、返回列表结果的根字段如usernames(names: [String!]!): [Actor]其要求是响应列表长度必须与入参列表一致且响应顺序与入参顺序一一对应即任意置换输入输出也随之置换。在 Relay 自身的测试 Schema 中可以看到这一契约的落地形态testschema.graphql 的Query类型上同时声明了node(id: ID): Node、node_id_required(id: ID!): Node与复数形式nodes(ids: [ID!]): [Node]而Bicycle、User等类型都通过implements Node暴露全局唯一id: ID!。Connections标准化的切片与分页模型一个派系拥有多艘飞船。Relay 内置了一套让一对多关系易于操作的功能它使用一种标准化的方式来表达一对多关系——即连接Connection模型。这个标准模型提供了对结果集进行切片slicing和分页paginating的途径。第一步用first做切片先查询 Rebels 的第一艘飞船query RebelsShipsQuery { rebels { name ships(first: 1) { edges { node { name } } } } }返回{ rebels: { name: Alliance to Restore the Republic, ships: { edges: [ { node: { name: X-Wing } } ] } } }这里通过ships的first参数把结果集切片为第一条。但如果要翻页呢每条边上都会暴露一个可用来翻页的游标cursor。第二步用游标cursor分页这次请求前两条并同时取出游标query MoreRebelShipsQuery { rebels { name ships(first: 2) { edges { cursor node { name } } } } }返回{ rebels: { name: Alliance to Restore the Republic, ships: { edges: [ { cursor: YXJyYXljb25uZWN0aW9uOjA, node: { name: X-Wing } }, { cursor: YXJyYXljb25uZWN0aW9uOjE, node: { name: Y-Wing } } ] } } }注意游标也是 base64 字符串——与前面 ID 的模式一致服务端在提醒我们这是不透明字符串。我们可以把这个字符串作为after参数回传给ships字段请求上一条结果中最后一条之后的接下来三条飞船query EndOfRebelShipsQuery { rebels { name ships(first: 3 after: YXJyYXljb25uZWN0aW9uOjE) { edges { cursor node { name } } } } }返回{ rebels: { name: Alliance to Restore the Republic, ships: { edges: [ { cursor: YXJyYXljb25uZWN0aW9uOjI, node: { name: A-Wing } }, { cursor: YXJyYXljb25uZWN0aW9uOjM, node: { name: Millennium Falcon } }, { cursor: YXJyYXljb25uZWN0aW9uOjQ, node: { name: Home One } } ] } } }继续翻页请求接下来的四条query RebelsQuery { rebels { name ships(first: 4 after: YXJyYXljb25uZWN0aW9uOjQ) { edges { cursor node { name } } } } }返回{ rebels: { name: Alliance to Restore the Republic, ships: { edges: [] } } }没有更多飞船了——系统里 Rebels 只有五艘飞船。如果能不经过又一次往返就知道已经到达连接末尾就好了。连接模型通过PageInfo类型暴露了这一能力。第三步用PageInfo.hasNextPage判断是否还有下一页再发出那两条取飞船的查询但这次加上hasNextPagequery EndOfRebelShipsQuery { rebels { name originalShips: ships(first: 2) { edges { node { name } } pageInfo { hasNextPage } } moreShips: ships(first: 3 after: YXJyYXljb25uZWN0aW9uOjE) { edges { node { name } } pageInfo { hasNextPage } } } }返回{ rebels: { name: Alliance to Restore the Republic, originalShips: { edges: [ { node: { name: X-Wing } }, { node: { name: Y-Wing } } ], pageInfo: { hasNextPage: true } }, moreShips: { edges: [ { node: { name: A-Wing } }, { node: { name: Millennium Falcon } }, { node: { name: Home One } } ], pageInfo: { hasNextPage: false } } } }第一次查飞船时GraphQL 告诉我们还有下一页下一次则告诉我们已经到达连接末尾。Relay 正是利用这些能力在连接之上构建抽象让客户端无需手动管理游标即可高效工作。从规范到实现Relay 如何消费连接契约理解了连接契约之后再看 Relay 运行时与编译期是如何消费它的会让整条链路更完整。运行时ConnectionInterface 与 ConnectionHandlerRelay 运行时把连接模型的具体字段名抽象为一份可注入的配置。在 ConnectionInterface.js 中默认配置把edges、pageInfo、cursor、node、hasNextPage、hasPreviousPage、startCursor、endCursor等字段名映射为运行时内部使用的常量并定义了isConnectionCall()用于识别first/last/before/after/find/surrounds等连接调用——凡是仅因连接调用参数不同而不同的字段在 Relay 中被视为同一身份的字段。而 ConnectionHandler.js 则实现了连接的标准数据处理getConnectionID()依据记录 ID、key 与过滤条件生成稳定的连接存储 IDinsertEdgeAfter()/insertEdgeBefore()用于在本地存储中把新边插入连接配合变更操作的 optimistic update 使用createEdge()/buildConnectionEdge()把服务端返回的 edge 记录转换成客户端存储所需的形态deleteNode()从连接中移除某个节点。也就是说当服务端严格按规范返回edges/pageInfo/cursor结构时Relay 就能把游标分页与变更后的边插入/删除自动化这也是客户端无需手动管理游标的底层支撑。编译期relay-config 的 connectionInterface 配置Relay 编译器同样感知连接模型。在 connection_interface.rs 中ConnectionInterface结构体定义了cursor、edges、endCursor、hasNextPage、hasPreviousPage、node、pageInfo、startCursor八个字段名其默认值与运行时完全一致如hasNextPage、startCursor等。它作为 project_config.rs 中connectionInterface配置项存在允许在 Schema 使用非标准字段名时如has_next_page通过配置文件覆盖。重要约定当在编译器配置中修改了connectionInterface必须同时在运行时调用ConnectionInterface.inject()传入相同的值否则编译器生成的产物与运行时对连接字段的解析将不一致源码注释对此有明确说明见 connection_interface.rs 第 16-18 行。此外编译器在 subschema_extraction.rs 中会再水合被裁剪掉的分页所需的PageInfo字段确保生成的分页请求总是包含hasNextPage等必需字段。分页变量的生成在客户端侧Relay 通过 getPaginationVariables.js 根据翻页方向forward/backward自动构造分页变量向前翻页时填充first/after对应的 cursor 与 count 变量向后翻页时填充last/before并自动把另一方向的变量置空getPaginationMetadata.js 则从 fragment 的元数据中解析出连接路径与分页请求。这些运行时逻辑之所以成立前提正是服务端遵循了first/after/last/before参数与edges/cursor/pageInfo响应的标准契约。附录连接模型的正式规范要点本仓库 Connections.md 收录了完整的 GraphQL Cursor Connections 规范供 Relay 使用的版本其要点如下可作为服务端实现的核对清单保留类型所有名称以 Connection 结尾的对象类型以及名为PageInfo的对象均为规范保留类型连接类型Connection Types必须包含edges返回列表元素为边类型与pageInfo返回非空PageInfo两个字段可自行添加其他与连接相关的字段边类型Edge Types必须包含node返回标量、枚举、对象、接口、联合或它们的非空包装不能返回列表与cursor序列化为字符串的类型两个字段分页参数返回连接类型的字段必须提供前向分页参数first非负整数after游标类型、后向分页参数last非负整数before游标类型或两者都提供。分页时一般把上一页最后一条边的cursor传给after把下一页第一条边的cursor传给before分页算法服务端先应用before/after游标过滤边after之前的边连同afterEdge本身被移除before之后的边连同beforeEdge本身被移除再按first从末尾切片最后按last从开头切片。注意同时传入first与last不被鼓励边顺序分页顺序可以由业务逻辑决定但必须跨页保持一致且使用first/after与使用last/before时的边顺序应相同不应反向PageInfo必须包含hasPreviousPage与hasNextPage均返回非空布尔以及startCursor与endCursor均返回不透明字符串无结果时可为空。startCursor/endCursor分别是edges中第一条与最后一条边对应的游标。规范还指出Relay 早期版本依赖逐边选择cursor而 Relay Modern 改为选择startCursor/endCursor以节省带宽。进一步阅读本文是对 GraphQL 服务端规范的概览。如需 Relay 兼容服务端的详细要求可继续阅读Relay Cursor Connections 规范仓库内副本连接模型的正式要求、分页算法与PageInfo定义GraphQL Global Object Identification 规范仓库内副本Node接口、node根字段、字段稳定性与复数标识根字段的正式要求运行时连接实现ConnectionHandler.js 与 ConnectionInterface.js编译器连接配置connection_interface.rs 与 project_config.rs端到端示例Relay 测试 Schema testschema.graphql 中Node接口、node/nodes根字段与各类Connection的完整落地形态。赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐Relay GraphQL 服务端规范指南Object Identification 与 Connections 的完整实现要点Relay GraphQL 服务端规范指南Object Identification 与 Connections 的完整实现要点 本篇指南围绕 Relay 对前端开发工具Relay GraphQL Server Specification 全解读对象标识Object Identification与连接Connections两大核心假设Relay GraphQL Server Specification 全解读对象标识Object Identification与连接Connection前端开发工具Relay 的 GraphQL 服务器规范Object Identification 与 Connection 分页实战指南Relay 的 GraphQL 服务器规范Object Identification 与 Connection 分页实战指南 本文档旨在阐明 Relay 对前端开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考