Relay 数据更新完全指南:Mutation、Subscription 与本地存储更新机制
Relay 数据更新完全指南Mutation、Subscription 与本地存储更新机制【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relayRelay 在客户端维护一个归一化的内存数据存储normalized in-memory store它随着应用中不断发起的 GraphQL 操作查询、变更、订阅持续累积数据相当于一个本地 GraphQL 数据库。本指南基于 Relay v13 官方文档 guided-tour/updating-data 展开系统讲解如何在更新服务器数据的同时保持本地数据存储与组件渲染同步——读完本文你将掌握 Mutation 的声明与执行、四种数据更新方式、updater 函数、乐观更新、Subscription、纯本地更新以及客户端扩展数据的完整实战方案。数据更新的核心心智模型理解 Relay 的数据更新机制首先要建立三个基本认知归一化存储Relay 的 store 是一个本地内存数据库GraphQL 操作返回的数据会被拆解为以id为键的记录record进行归一化存储而非整棵树原样保存。任何组件查询的数据都来自这份共享的 store。自动订阅与重渲染当 store 中的记录被更新时所有订阅了受影响数据的组件都会收到通知并使用最新数据自动重新渲染。服务端与本地两条线数据更新分为更新服务器数据Mutation / Subscription与更新本地数据commitLocalUpdate/commitPayload等两条路径但它们最终都作用于同一个 store遵循同一套通知-重渲染机制。本小节对应文档原文introduction.mdRelay 持有归一化的 GraphQL 数据存储随着查询不断累积当记录被更新时受影响组件会被通知并以最新数据重渲染。后续内容围绕更新服务器 同步本地 store展开。用 GraphQL Mutation 更新服务器数据在 GraphQL 中服务器数据通过Mutation更新。Mutation 是可读可写的服务器操作它既在后端修改数据又允许在同一请求中查询修改后的数据。编写 MutationMutation 与查询query写法几乎相同唯一区别是使用mutation关键字mutation FeedbackLikeMutation($input: FeedbackLikeData!) { feedback_like(data: $input) { feedback { id viewer_does_like like_count } } }关键要点上面的 Mutation 将指定的Feedback对象标记为已点赞。feedback_like是mutation 根字段mutation field接收特定输入由服务器处理后端数据。Mutation 分两步处理先执行服务器端更新再执行查询。这保证了你在响应中看到的总是更新后的数据。Mutation 字段返回特定 GraphQL 类型暴露了可在响应中查询的字段集合。注意Mutation 字段上可访问的字段并不自动等同于普通查询可访问的字段。最佳实践是在 Mutation 响应中带上viewer对象与所有被更新的实体entity。上面的例子查询了更新后的 feedback 对象包括更新后的like_count以及表示当前 viewer 是否已点赞的viewer_does_like。一次成功响应的示例{ feedback_like: { feedback: { id: feedback-id, viewer_does_like: true, like_count: 1 } } }在 Relay 中声明与执行 Mutation在 Relay 中Mutation 同样通过graphql标签声明const {graphql} require(react-relay); const feedbackLikeMutation graphql mutation FeedbackLikeMutation($input: FeedbackLikeData!) { feedback_like(data: $input) { feedback { id viewer_does_like like_count } } } ;Mutation 与查询、fragment 一样可以引用 GraphQL 变量。执行 Mutation 有两条 API 路径commitMutation命令式与useMutationHooks 式。先看commitMutation的完整例子import type {Environment} from react-relay; import type {FeedbackLikeData, FeedbackLikeMutation} from FeedbackLikeMutation.graphql; const {commitMutation, graphql} require(react-relay); function commitFeedbackLikeMutation( environment: Environment, input: FeedbackLikeData, ) { return commitMutationFeedbackLikeMutation(environment, { mutation: graphql mutation FeedbackLikeMutation($input: FeedbackLikeData!) { feedback_like(data: $input) { feedback { id viewer_does_like like_count } } } , variables: {input}, onCompleted: response {} /* Mutation completed */, onError: error {} /* Mutation errored */, }); } module.exports {commit: commitFeedbackLikeMutation};要点拆解commitMutation接收 environment、graphql标签声明的 mutation以及发送请求所需的 variables。Mutation 的input可以由编译器生成的FeedbackLikeMutation.graphql模块提供 Flow 类型。Relay 在构建期为所有 Mutation 生成类型命名格式为*mutation_name*.graphql.js。variables、onCompleted中的response以及optimisticResponse都会通过单个自动生成类型如FeedbackLikeMutation获得强类型。若想为optimisticResponse字段生成强类型需要在 mutation 查询根上添加raw_response_type指令。onCompleted与onError分别在请求成功与出错时被调用。自动合并收到 Mutation 响应后响应中带id且与 store 中记录匹配的对象会自动以响应中的新字段值更新。本例会找到 store 中 id 匹配的Feedback对象更新其viewer_does_like与like_count。由 Mutation 引起的任何本地数据更新都会自动通知订阅该数据的组件并触发重渲染。从源码看commitMutation 会先校验 environment 与操作类型operationKind ! mutation时抛错然后通过createOperationDescriptor构造操作描述符最终调用environment.executeMutation(...)并订阅其结果。onCompleted回调中的数据来自environment.lookup(operation.fragment)——也就是说onCompleted拿到的是 mutation 写入 store之后的最终快照数据。使用 useMutation Hook在函数组件中更推荐使用 useMutation Hook它会自动从useRelayEnvironment()获取 environment并返回[commit, isMutationInFlight]二元组。从源码 useMutation 可以看出第二个返回值跟踪当前是否有 mutation 在飞行中isMutationInFlight内部通过inFlightMutationsRef记录所有未完成的 disposable并在onCompleted/onError/onUnsubscribe时清理。它本质上是对commitMutation的薄封装因此所有配置项optimisticResponse、optimisticUpdater、updater等均一致。请求完成后更新 store 的四种方式当 Mutation 请求完成store 数据会通过以下四种方式之一或组合被更新自动字段合并从 Mutation 字段内查询的、包含id字段的字段其 store 记录会自动用响应中的新值更新。例如上面包含feedback { id }的查询Relay 会找到 store 中 id 匹配的Feedback记录并更新viewer_does_like与like_count。提示与其在 mutation 完成后重新 refetch 某个 fragment不如直接把该 fragment spread 进 mutation 响应让 fragment 数据随同一请求被更新。deleteRecord指令从 Mutation 字段内查询的、包含id且带有deleteRecord指令的字段会从 store 中删除。prependEdge/appendEdge指令从 Mutation 字段内查询的、带有prependEdge或appendEdge指令的 edge 字段会被分别前置或追加到某个 connection 上。updater 函数以上三种方式覆盖不了的更新场景可提供updater函数对本地 store 进行完全控制。关于多种方式并存时的执行顺序参见后文updater 函数的执行顺序一节。Updater 函数对 store 的完全控制如果本地数据更新比更新字段值更复杂且无法由上述声明式指令处理可以给commitMutation或useMutation提供updater函数import type {Environment} from react-relay; import type {CommentCreateData, CreateCommentMutation} from CreateCommentMutation.graphql; const {commitMutation, graphql} require(react-relay); const {ConnectionHandler} require(relay-runtime); function commitCommentCreateMutation( environment: Environment, feedbackID: string, input: CommentCreateData, ) { return commitMutationCreateCommentMutation(environment, { mutation: graphql mutation CreateCommentMutation($input: CommentCreateData!) { comment_create(input: $input) { comment_edge { cursor node { body { text } } } } } , variables: {input}, onCompleted: () {}, onError: error {}, updater: store { const feedbackRecord store.get(feedbackID); // Get connection record const connectionRecord ConnectionHandler.getConnection( feedbackRecord, CommentsComponent_comments_connection, ); // Get the payload returned from the server const payload store.getRootField(comment_create); // Get the edge inside the payload const serverEdge payload.getLinkedRecord(comment_edge); // Build edge for adding to the connection const newEdge ConnectionHandler.buildConnectionEdge( store, connectionRecord, serverEdge, ); // Add edge to the end of the connection ConnectionHandler.insertEdgeAfter( connectionRecord, newEdge, ); }, }); } module.exports {commit: commitCommentCreateMutation};要点拆解updater接收一个store参数它是RecordSourceSelectorProxy的实例。该接口允许你以命令式方式直接读写 Relay store可以创建全新记录也可以更新或删除已有记录。updater还接收第二个payload参数即 mutation 响应对象用于不经过 store 直接读取 payload 数据。本例在服务器成功添加评论后把新评论加入本地 store 的 connection 中。connection 增删的细节参见 Updating Connections。本例其实无需updater——用appendEdge指令会更合适mutation 响应是root field记录可通过store.getRootFieldAPI 读取。本例读取的是comment_create根字段。重要mutation 的root与 query 的root不同mutation updater 中的store.getRootField只能拿到 mutation 响应中的记录要获取不在 mutation 响应中的根记录应使用store.getRoot().getLinkedRecord。updater 引起的任何本地数据更新都会自动通知订阅组件并触发重渲染。乐观更新提升感知响应速度很多时候我们不想等服务器响应才响应用户交互。比如用户点击点赞按钮我们希望在 mutation 响应返回前就立即把已点赞状态展示出来。这类场景应当乐观地optimistically更新本地数据使其立即反映 mutation成功后的预期状态若 mutation 最终失败再回滚更改并展示错误。Relay 提供了两个 API 来实现乐观更新optimisticResponse可预测响应时的最简单方案当你能预测 mutation 的服务器响应时提供optimisticResponse即可import type {Environment} from react-relay; import type {FeedbackLikeData, FeedbackLikeMutation} from FeedbackLikeMutation.graphql; const {commitMutation, graphql} require(react-relay); function commitFeedbackLikeMutation( environment: Environment, feedbackID: string, input: FeedbackLikeData, ) { return commitMutationFeedbackLikeMutation(environment, { mutation: graphql mutation FeedbackLikeMutation($input: FeedbackLikeData!) raw_response_type { feedback_like(data: $input) { feedback { id viewer_does_like } } } , variables: {input}, optimisticResponse: { feedback_like: { feedback: { id: feedbackID, viewer_does_like: true, }, }, }, onCompleted: () {} /* Mutation completed */, onError: error {} /* Mutation errored */, }); } module.exports {commit: commitFeedbackLikeMutation};行为说明optimisticResponse是一个与 mutation 响应形状一致的对象模拟一次成功的服务器响应。提供它之后Relay 会像处理真实服务器响应一样处理它并更新数据即合并匹配 id 记录的字段值。本例会立即将Feedback对象的viewer_does_like设为trueUI 立刻反映该变化。若 mutation成功乐观更新被回滚然后应用服务器真实响应。若 mutation失败乐观更新被回滚错误通过onError回调传达。添加raw_response_type指令后会为optimisticResponse生成类型。optimisticUpdater复杂乐观更新某些场景无法静态预测服务器响应或需要执行更复杂的乐观更新删除/创建记录、增删 connection 条目等。此时可提供optimisticUpdater。例如除了设置viewer_does_like为 true还希望用optimisticUpdater递增like_count因为该值需要先从 store 读取optimisticResponse无法静态给出import type {Environment} from react-relay; import type {FeedbackLikeData} from FeedbackLikeMutation.graphql; const {commitMutation, graphql} require(react-relay); function commitFeedbackLikeMutation( environment: Environment, feedbackID: string, input: FeedbackLikeData, ) { return commitMutation(environment, { mutation: graphql mutation FeedbackLikeMutation($input: FeedbackLikeData!) { feedback_like(data: $input) { feedback { id like_count viewer_does_like } } } , variables: {input}, optimisticUpdater: store { // Get the record for the Feedback object const feedbackRecord store.get(feedbackID); // Read the current value for the like_count const currentLikeCount feedbackRecord.getValue(like_count); // Optimistically increment the like_count by 1 feedbackRecord.setValue((currentLikeCount ?? 0) 1, like_count); // Optimistically set viewer_does_like to true feedbackRecord.setValue(true, viewer_does_like); }, onCompleted: () {} /* Mutation completed */, onError: error {} /* Mutation errored */, }); } module.exports {commit: commitFeedbackLikeMutation};要点optimisticUpdater与普通updater签名一致、行为相同主要区别是它在 mutation 响应完成之前立即执行。mutation 成功后乐观更新回滚应用服务器响应。注意服务器最终值可能与本地乐观预测不同例如同时有其他点赞发生服务器like_count可能递增超过 1。mutation 失败后乐观更新回滚错误通过onError传达。本例没有提供updater这没关系——服务器响应到达时仍会应用默认行为合并Feedback对象上like_count与viewer_does_like的新字段值。:::note 记住由 mutation 引起的任何本地数据更新都会自动通知并重渲染订阅了该数据的组件。 :::updater 函数的执行顺序一般来说updater与乐观更新的执行顺序如下若提供了optimisticResponseRelay 先用它合并匹配 id 记录的新字段值。若提供了optimisticUpdaterRelay 执行它并相应更新 store。若提供了optimisticResponse声明式 mutation 指令deleteRecord、appendEdge、prependEdge会在乐观响应上被处理。若 mutation 请求成功已应用的任何乐观更新被回滚。Relay 用服务器响应合并匹配 id 记录的新字段值。若提供了updaterRelay 执行它更新 store服务器 payload 作为 store 中的 root field 提供给updater。Relay 处理deleteRecord、appendEdge、prependEdge声明式指令。若 mutation 请求失败已应用的任何乐观更新被回滚。onError回调被调用。完整示例三者并用复杂场景下可以同时提供optimisticResponse、optimisticUpdater与updater。以添加评论为例connection 更新细节参见 Updating Connectionsimport type {Environment} from react-relay; import type {CommentCreateData, CreateCommentMutation} from CreateCommentMutation.graphql; const {commitMutation, graphql} require(react-relay); const {ConnectionHandler} require(relay-runtime); function commitCommentCreateMutation( environment: Environment, feedbackID: string, input: CommentCreateData, ) { return commitMutationCreateCommentMutation(environment, { mutation: graphql mutation CreateCommentMutation($input: CommentCreateData!) { comment_create(input: $input) { feedback { id viewer_has_commented } comment_edge { cursor node { body { text } } } } } , variables: {input}, onCompleted: () {}, onError: error {}, // Optimistically set the value for viewer_has_commented optimisticResponse: { feedback: { id: feedbackID, viewer_has_commented: true, }, }, // Optimistically add a new comment to the comments connection optimisticUpdater: store { const feedbackRecord store.get(feedbackID); const connectionRecord ConnectionHandler.getConnection( userRecord, CommentsComponent_comments_connection, ); // Create a new local Comment from scratch const id client:new_comment:${randomID()}; const newCommentRecord store.create(id, Comment); // ... update new comment with content // Create new edge from scratch const newEdge ConnectionHandler.createEdge( store, connectionRecord, newCommentRecord, CommentEdge /* GraphQl Type for edge */, ); // Add edge to the end of the connection ConnectionHandler.insertEdgeAfter(connectionRecord, newEdge); }, updater: store { const feedbackRecord store.get(feedbackID); const connectionRecord ConnectionHandler.getConnection( userRecord, CommentsComponent_comments_connection, ); // Get the payload returned from the server const payload store.getRootField(comment_create); // Get the edge from server payload const newEdge payload.getLinkedRecord(comment_edge); // Add edge to the end of the connection ConnectionHandler.insertEdgeAfter(connectionRecord, newEdge); }, }); } module.exports {commit: commitCommentCreateMutation};按执行顺序拆解提供optimisticResponse所以它最先执行viewer_has_commented的新值被合并进已有Feedback对象设为true。提供optimisticUpdater接下来执行从零创建新的 comment 与 edge 记录模拟服务器响应中的新 edge 形状并把它插入 connection。乐观更新结束后订阅该数据的组件收到通知。mutation 成功时所有乐观更新回滚。服务器响应被 Relay 处理viewer_has_commented新值合并进Feedback对象设为true。最后执行updater与optimisticUpdater类似但它不是从零造数据而是从 mutation payload 读取新 edge 并插入 connection。Mutation 期间的数据失效Invalidation推荐做法是在执行 mutation 时把受其影响的所有相关数据作为 mutation body 请求回来以保证本地 store 与服务器状态一致。但对于连锁影响面大的 mutation例如拉黑用户退出群组提前穷举所有受影响数据往往不可行。此时更直接的做法是显式标记部分数据甚至整个 store为过期stale让 Relay 在下次渲染时重新获取。可用的数据失效 API 参见 Staleness of Data。Mutation 排队TBD该能力留给用户空间实现Relay v13 尚未提供内置的 mutation 排队机制。如果需要串行/排队执行 mutation需要在应用层自行实现。GraphQL Subscription订阅服务器数据变化GraphQL Subscription 是一种让客户端订阅服务器某段数据变化、并在变化时收到通知的机制。它看起来与 query 几乎相同区别是使用subscription关键字subscription FeedbackLikeSubscription($input: FeedbackLikeSubscribeData!) { feedback_like_subscribe(data: $input) { feedback { id like_count } } }订阅上述 subscription 后每当指定Feedback对象被点赞或取消点赞客户端都会收到通知。feedback_like_subscribe是 subscription 字段本身接收特定输入并在后端建立订阅。subscription 字段返回特定 GraphQL 类型暴露了可查询的 payload 字段客户端被通知时会收到该 subscription payload。本例查询了带更新后like_count的 Feedback 对象从而实时展示点赞数。客户端收到的 payload 示例{ feedback_like_subscribe: { feedback: { id: feedback-id, like_count: 321 } } }在 Relay 中同样用graphql标签声明且可引用 variables。用 requestSubscription 执行订阅import type {Environment} from react-relay; import type {FeedbackLikeSubscribeData} from FeedbackLikeSubscription.graphql; const {graphql, requestSubscription} require(react-relay); function feedbackLikeSubscribe( environment: Environment, feedbackID: string, input: FeedbackLikeSubscribeData, ) { return requestSubscription(environment, { subscription: graphql subscription FeedbackLikeSubscription( $input: FeedbackLikeSubscribeData! ) { feedback_like_subscribe(data: $input) { feedback { id like_count } } } , variables: {input}, onCompleted: () {} /* Subscription established */, onError: error {} /* Subscription errored */, onNext: response {} /* Subscription payload received */ }); } module.exports {subscribe: feedbackLikeSubscribe};要点requestSubscription接收 environment、graphql标签声明的 subscription 与 variables。subscription 的input可由FeedbackLikeSubscription.graphql模块提供 Flow 类型Relay 在构建期按*subscription_name*.graphql.js格式生成类型。onCompleted与onError分别在订阅成功建立或出错时调用onNext在每次收到 subscription payload 时调用。自动更新收到 subscription payload 时若 payload 中的对象带有 ID本地 store 中的记录会_自动_以 payload 中的新字段值更新。本例会自动找到 store 中 id 匹配的Feedback对象更新like_count。subscription 引起的本地数据更新会自动通知订阅组件并触发重渲染。若需要在订阅响应中执行比更新字段值更复杂的操作删除/创建记录、增删 connection 条目可以给requestSubscription提供updater函数import type {Environment} from react-relay; import type {CommentCreateSubscribeData} from CommentCreateSubscription.graphql; const {graphql, requestSubscription} require(react-relay); function commentCreateSubscribe( environment: Environment, feedbackID: string, input: CommentCreateSubscribeData, ) { return requestSubscription(environment, { subscription: graphql subscription CommentCreateSubscription( $input: CommentCreateSubscribeData! ) { comment_create_subscribe(data: $input) { feedback_comment_edge { cursor node { body { text } } } } } , variables: {input}, updater: store { const feedbackRecord store.get(feedbackID); // Get connection record const connectionRecord ConnectionHandler.getConnection( feedbackRecord, CommentsComponent_comments_connection, ); // Get the payload returned from the server const payload store.getRootField(comment_create_subscribe); // Get the edge inside the payload const serverEdge payload.getLinkedRecord(feedback_comment_edge); // Build edge for adding to the connection const newEdge ConnectionHandler.buildConnectionEdge( store, connectionRecord, serverEdge, ); // Add edge to the end of the connection ConnectionHandler.insertEdgeAfter(connectionRecord, newEdge); }, onCompleted: () {} /* Subscription established */, onError: error {} /* Subscription errored */, onNext: response {} /* Subscription payload received */, }); } module.exports {subscribe: commentCreateSubscribe};updater的store参数同样是RecordSourceSelectorProxy实例可命令式读写 store拥有完全控制权。订阅 payload 是root field记录可通过store.getRootField读取本例读取comment_create_subscribe根字段。完整 store 读写 API 参见 store API 参考。从源码看requestSubscription 同样校验operationKind为subscription构造 operation 后调用environment.executeSubscription({operation, updater})。它返回{dispose: sub.unsubscribe}可用于主动取消订阅。onNext中通过environment.lookup(selector)读取写入 store 后的最新数据。用 useSubscription Hook 订阅在函数组件中可以使用useSubscriptionimport {graphql, useSubscription} from react-relay; import {useMemo} from react; const subscription graphqlsubscription ...; function MyFunctionalComponent({ id }) { // IMPORTANT: your config should be memoized, or at least not re-computed // every render. Otherwise, useSubscription will re-render too frequently. const config useMemo(() { variables: { id }, subscription }, [id]); useSubscription(config); return divMove Fast/div }它是requestSubscription的薄封装行为如下组件挂载时按给定 config 订阅。组件卸载时取消订阅。如果需求更复杂如命令式发起订阅请直接使用requestSubscriptionAPI。更多细节参见 useSubscription API 参考。配置订阅的网络层使用 subscription 前需要先配置 Network Layer 以支持订阅。GraphQL 订阅通常通过 WebSocket 通信以下是基于graphql-ws的示例import { ... Network, Observable } from relay-runtime; import { createClient } from graphql-ws; const wsClient createClient({ url:ws://localhost:3000, }); const subscribe (operation, variables) { return Observable.create((sink) { return wsClient.subscribe( { operationName: operation.name, query: operation.text, variables, }, sink, ); }); } const network Network.create(fetchQuery, subscribe);也可以使用较老的subscriptions-transport-ws库import { ... Network, Observable } from relay-runtime; import { SubscriptionClient } from subscriptions-transport-ws; ... const subscriptionClient new SubscriptionClient(ws://localhost:3000, { reconnect: true, }); const subscribe (request, variables) { const subscribeObservable subscriptionClient.request({ query: request.text, operationName: request.name, variables, }); // Important: Convert subscriptions-transport-ws observable type to Relays return Observable.from(subscribeObservable); }; const network Network.create(fetchQuery, subscribe); ...注意两种方式都通过Network.create(fetchQuery, subscribe)将订阅函数接入 Relay 网络层。纯本地更新commitLocalUpdate 与 commitPayload除服务器操作外Relay 还提供 API 进行纯本地的 store 更新不绑定任何服务器操作。本地更新既可以作用在 client-only 数据 上也可以作用在从服务器获取的常规数据上。commitLocalUpdate用updater函数风格做本地更新可以使用commitLocalUpdateAPIimport type {Environment} from react-relay; const {commitLocalUpdate, graphql} require(react-relay); function commitCommentCreateLocally( environment: Environment, feedbackID: string, ) { return commitLocalUpdate(environment, store { const feedbackRecord store.get(feedbackID); const connectionRecord ConnectionHandler.getConnection( userRecord, CommentsComponent_comments_connection, ); // Create a new local Comment from scratch const id client:new_comment:${randomID()}; const newCommentRecord store.create(id, Comment); // ... update new comment with content // Create new edge from scratch const newEdge ConnectionHandler.createEdge( store, connectionRecord, newCommentRecord, CommentEdge /* GraphQl Type for edge */, ); // Add edge to the end of the connection ConnectionHandler.insertEdgeAfter(connectionRecord, newEdge); }); } module.exports {commit: commitCommentCreateLocally};commitLocalUpdate只接收 environment 与 updater 函数。updater的store参数是RecordSourceSelectorProxy实例可命令式读写 store创建新记录、更新或删除已有记录。本例在本地 store 中向 connection 添加新评论connection 增删细节参见 Updating Connections。任何本地数据更新都会自动通知订阅组件并触发重渲染。从源码看commitLocalUpdate 的实现非常薄——它直接委托给environment.commitUpdate(updater)真正的写入由环境内部基于RecordSourceProxy完成。commitPayloadcommitPayload接收一个OperationDescriptor与对应的 query payload并将其写入 Relay Store。该 payload 会像普通 query 的服务器响应一样被解析同时也会解析以JSResource、requireDefer等形式传入的 Data Driven Dependenciesimport type {FooQueryRawResponse} from FooQuery.graphql const {createOperationDescriptor} require(relay-runtime); const operationDescriptor createOperationDescriptor(FooQuery, { id: an-id, otherVariable: value, }); const payload: FooQueryRawResponse {...}; environment.commitPayload(operation, payload);OperationDescriptor可通过createOperationDescriptor创建它接收 query 与 query 变量。payload 可用raw_response_type指令生成的 Flow 类型进行类型标注。任何本地数据更新都会自动通知订阅组件并触发重渲染。commitPayload常用于测试或注入本地已知数据确保组件可以脱离网络直接消费 store 中的数据。客户端扩展数据Client-Only DataRelay 支持通过client schema extensions在客户端浏览器内扩展 GraphQL schema以建模仅需在客户端创建、读取、更新的数据。它可以给服务器拉取的数据附加小段信息也可以完整建模客户端专属状态并交给 Relay 存储管理。Client schema extensions 既可以修改已有类型给类型加新字段也可以创建仅存在于客户端的新类型。扩展已有类型在--src源码目录中添加.graphql文件extend type Comment { is_new_comment: Boolean }用extend关键字扩展已有类型给Comment添加新字段is_new_comment。之后可以在组件中读取并可用正常 Relay API 在必要时更新。例如用它为新评论渲染不同的视觉样式并在创建新评论时设置该字段。添加新类型在.graphql文件中用常规 GraphQL 语法定义新类型一个文件可定义多个类型# You can define more than one type in a single file enum FetchStatus { FETCHED PENDING ERRORED } type FetchState { # You can reuse client types to define other types status: FetchStatus # You can also reference regular server types started_by: User! } extend type Item { # You can extend server types with client-only types fetch_state: FetchState }本例定义了 2 个新的 client-only 类型一个enum与一个普通type。它们可以正常互相引用也可以引用服务器定义的类型还可以扩展服务器类型并添加 client-only 类型的字段。这些数据同样可以读取与更新。读取客户端扩展数据在 fragments 或 queries 中像普通字段一样选择 client-only 字段const data useFragment( graphql fragment CommentComponent_comment on Comment { # We can select client-only fields as we would any other field is_new_comment body { text } } , props.user, );更新客户端扩展数据更新 client-only 数据的方式与常规数据一致可以在 mutation 或 subscription 的 updater 中更新也可以使用本地更新原语commitLocalUpdate/commitPayload更新。更全面的 client schema extensions 指南参见 guides/client-schema-extensions.md。小结与实践建议场景推荐方案更新服务器数据命令式commitMutation更新服务器数据React 组件内useMutation可预测响应的即时反馈optimisticResponse可加raw_response_type获得类型复杂即时反馈依赖 store 现值 / 增删 connectionoptimisticUpdater复杂响应后处理updaterstore.getRootField声明式更新 connectionappendEdge/prependEdge删除记录deleteRecord订阅服务器数据变化命令式requestSubscription订阅服务器数据变化组件内useSubscriptionconfig 务必 memoize纯本地写操作commitLocalUpdate本地注入完整 payloadenvironment.commitPayload模型仅客户端状态client schema extensions 上述本地更新原语核心原则始终不变任何对本地 store 的数据更新无论来自 mutation、subscription 还是本地 API都会自动通知订阅了该数据的组件并触发重渲染——这正是 Relay 数据驱动 UI 保持同步的根本保证。相关阅读GraphQL Mutations 完整指南GraphQL Subscriptions 完整指南本地数据更新Client-Only DataUpdating Connections数据过期与失效Staleness of Datastore API 参考useMutation API 参考useSubscription API 参考【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relay创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考