尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Relay 数据更新完全指南:从 GraphQL Mutations 到 Store 命令式修改

Relay 数据更新完全指南:从 GraphQL Mutations 到 Store 命令式修改 前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载在 Relay 中获取数据只是数据生命周期的一半。本指南基于 Relay 19 版本文档中 updating-data 章节入口文档 展开系统讲解如何有意地修改 Relay 本地数据存储Relay store以及服务器端数据包括使用useMutation/commitMutation执行 GraphQL Mutations、通过updater命令式改写 store、借助optimisticResponse实现乐观更新、用ConnectionHandler维护连接Connections以及通过commitLocalUpdate/commitPayload做纯本地更新。读完本文你将掌握一套从发出写请求到本地 store 与 UI 保持一致的完整数据更新方案。从获取数据到更新数据为什么需要专门讨论在 guided-tour 的获取数据章节 中我们讨论了如何用 GraphQL 查询获取数据。需要强调的是获取数据这件事附带地也可能修改 Relay 本地 store 中的数据——如果查询返回的数据与 store 中已有缓存不一致Relay 会合并并覆盖。但这不是有意的写入行为因此我们还需要一套机制来显式地、受控地更新本地数据与服务端数据这正是 updating-data 系列文档的主题。在进入细节之前先明确一个贯穿全文的核心概念:::noteRelay store是关联于某个 Relay environment 的 GraphQL 数据缓存其中保存的是应用执行过程中遇到过的即被查询过的数据。所有本地写入最终都会落到这个 store 上并驱动依赖这些数据的组件重渲染。 :::Relay 更新数据的整体能力图谱对应本仓库 versioned docs 中 updating-data 目录如下GraphQL Mutations通过useMutation/commitMutation执行服务端写操作并将响应写回 storeGraphQL Subscriptions通过useSubscription/requestSubscription订阅服务端事件流随事件更新数据命令式修改 store 数据在updater中使用readUpdatableFragment/readUpdatableQuery精确改写标量字段命令式修改链接字段通过assignable片段在 store 中重新指定记录间的链接关系更新连接Connections向连接增删边可用声明式指令或ConnectionHandler手工操作本地数据更新commitLocalUpdate与commitPayload两类纯本地 API客户端专属数据通过 client schema extensions 在客户端扩展 GraphQL schema类型安全 updaters FAQ解释updatable/readUpdatableQuery等类型安全 API 的设计动机与约束。下面按由简到繁的顺序逐一展开。GraphQL Mutations在 Relay 中声明并执行写操作在 GraphQL 中服务端数据通过mutation更新。Mutation 是一种可读可写的服务端操作它既在后台修改数据又允许在同一次请求中查询修改后的数据。编写 MutationMutation 的写法与 query 几乎一致只是关键字换成mutationmutation FeedbackLikeMutation($input: FeedbackLikeData!) { feedback_like(data: $input) { feedback { id viewer_does_like like_count } } }上面的 mutation 修改服务端数据把指定的Feedback对象标记为已点赞feedback_like是mutation 根字段mutation root field负责在后台更新数据Mutation 的处理分为两个独立步骤先处理服务端更新再执行查询。这保证了响应中只会出现已经被本次 mutation 更新过的数据。注查询本身也是这种外层先于内层的计算顺序只是约定上 mutation 顶层字段带有副作用mutation 根字段返回一个具体的 GraphQL 类型该类型暴露了可以在 mutation 响应中查询的数据。本例查询的是更新后的 feedback 对象包括最新的like_count和viewer_does_like表示当前 viewer 是否已点赞。一次成功响应的示例{ feedback_like: { feedback: { id: feedback-id, viewer_does_like: true, like_count: 1, } } }在 Relay 中同样用graphql标签来声明 mutation并且 mutation 可以像 query/fragment 一样引用 GraphQL variablesconst {graphql} require(react-relay); const feedbackLikeMutation graphql mutation FeedbackLikeMutation($input: FeedbackLikeData!) { feedback_like(data: $input) { feedback { id viewer_does_like like_count } } } ;使用useMutation执行 mutationRelay 提供了commitMutation和useMutation两个 API 来执行 mutation。先看 Hooks 风格的useMutationimport type {FeedbackLikeData, LikeButtonMutation} from LikeButtonMutation.graphql; const {useMutation, graphql} require(react-relay); function LikeButton({ feedbackId: string, }) { const [commitMutation, isMutationInFlight] useMutationLikeButtonMutation( graphql mutation LikeButtonMutation($input: FeedbackLikeData!) { feedback_like(data: $input) { feedback { viewer_does_like like_count } } } ); return button onClick{() commitMutation({ variables: { input: {id: feedbackId}, }, })} disabled{isMutationInFlight} Like /button }这段代码的要点useMutation的唯一参数是一个包含 mutation 的graphql字面量它返回一个二元组commit 回调接受一个UseMutationConfig配置对象与布尔值isMutationInFlight表示是否有 mutation 在途可用于禁用按钮useMutation还接受一个 Flow 类型参数如LikeButtonMutation。该类型由 Relay compiler 生成的graphql.js文件导出。最佳实践是始终提供这个类型参数——提供后UseMutationConfig也会被静态类型化调用commitMutation后Relay 发起网络请求执行feedback_like字段服务端记录用户已点赞并回选更新后的viewer_does_like与like_count。由于Feedback类型包含id字段Relay compiler 会自动为其补选id响应到达后Relay 在 store 中找到id匹配的 feedback 记录并更新字段值任何选择过这些字段的组件都会在值变化时自动重渲染。类型命名规则参数类型FeedbackLikeData的名字派生自顶层 mutation 字段名即feedback_like同样由生成的graphql.js文件导出。源码视角useMutation的实现在 React 侧useMutation的实现位于 packages/react-relay/relay-hooks/useMutation.js。其中UseMutationConfig类型L33-L51完整定义了可以传给 commit 回调的配置项configs?: ArrayDeclarativeMutationConfig声明式 mutation 配置旧式;onError?: ?(error: Error) void与onCompleted?: ?(response, errors) void成功/失败回调;optimisticResponse乐观响应依赖raw_response_type指令获得类型;optimisticUpdater?: ?SelectorStoreUpdater与updater?: ?SelectorStoreUpdater乐观/常规 updater 函数;uploadables?: UploadableMap文件上传;variables必填的 mutation 变量。hook 签名L66-L75返回[(UseMutationConfigInternal) Disposable, boolean]。其内部逻辑L107-L120是调用 commit 时先setMutationInFlight(true)然后委托给commitMutationFn默认是relay-runtime导出的commitMutation并在onCompleted/onError时通过cleanup把该 disposable 从在途集合中移除、更新isMutationInFlight。这正是按钮 disabled 状态随请求生命周期变化的实现基础。让组件响应 mutation手动选字段 vs 展开 fragment上例手动选择了viewer_does_like与like_count选择这些字段的组件会在值变化时重渲染。但更推荐的做法是展开对应组件的 fragment——因为组件选择的数据集是会演进的要求开发者记住所有可能影响自己数据的 mutation并持续维护正是 Relay 想要避免的全局推理负担。mutation FeedbackLikeMutation($input: FeedbackLikeData!) { feedback_like(data: $input) { feedback { ...FeedbackDisplay_feedback ...FeedbackDetail_feedback } } }这样FeedbackDisplay与FeedbackDetail组件所选字段会在 mutation 执行后一并刷新组件始终保持一致状态。:::note 展开 fragment 通常优于 mutation 完成后再手动 refetch更新后的数据可以在单次往返中取回。 :::在 mutation 完成或失败时执行回调UseMutationConfig支持以下回调onCompletedmutation 成功完成时执行参数是 mutation 响应在 fragment 展开边界处截断。传入的值是在 updaters 与声明式 mutation 指令应用之后、从 store 中读出的 mutation fragment——因此未展开 fragment 内的数据不会包含其中被删除的记录如deleteRecord删除的可能为nullonErrormutation 失败时执行参数是发生的错误对象。声明式 mutation 指令删除记录与操作连接除了手写 updaterRelay 提供了一组声明式指令让常见更新几乎零代码。删除记录deleteRecord想在 mutation 响应中把某个对象从 store 删除可在其id字段上加deleteRecordmutation DeletePostMutation($input: DeletePostData!) { delete_post(data: $input) { deleted_post { id deleteRecord } } }连接增删appendEdge/prependEdge/appendNode/prependNode/deleteEdge这些指令以及deleteEdge可以作用在 mutation、subscription 甚至 query 的字段上具体细节见 Updating Connections 文档此处先给出要点appendEdge/prependEdge作用于返回单个 edge 或 edge 列表的字段把选中的边追加到connections参数指定各连接的末尾/开头appendNode/prependNode作用于返回单个 node 或 node 列表的字段把选中的节点包装成边后追加/前插deleteEdge作用于返回ID或[ID]的字段删除各连接中节点 id 匹配的边它们都接受connections参数——一个包含连接 ID 的 GraphQL 变量。连接 ID 可通过连接字段的__id或ConnectionHandler.getConnectionIDAPI 获取。命令式修改 storeupdater函数当更新逻辑比写入响应字段更复杂且声明式指令无法覆盖时UseMutationConfig接受updater函数把 store 的写入完全交给你控制。详见 Imperatively modifying store data。什么时候用 updater复杂的客户端更新比简单地把网络响应写进 store 更复杂、且声明式指令搞不定的场景客户端 schema 扩展字段的初始化网络响应必然不包含 client schema extensions 中定义的字段可以用 updater 为其初始化数据其他 API 无法完成的操作如失效节点、删除节点、按字段查找所有连接等多个乐观响应修改同一个 store 值时若两个乐观响应各把点赞数 1第一个被回滚后第二个仍生效且不会被重算点赞数会保持 2——这种叠加场景下应使用乐观 updater详见下文乐观更新。什么时候不要用 updater触发其他副作用时应该用onCompleted回调。onCompleted保证只被调用一次而 updater/optimistic updater 可能被多次调用。类型安全的 updaterreadUpdatableFragment与updatable现代推荐写法是可更新的 fragment/query 类型安全的代理对象。以下例演示通过 schema extension 给Feedback增加is_new_comment字段并在 mutation updater 中把它置为true。首先定义 schema 扩展# Feedback.graphql extend type Feedback { is_new_comment: Boolean }然后在 mutation 的响应中展开一个updatable片段并在updater中读出并改写它// CreateFeedback.js import type {Environment} from react-relay; import type { FeedbackCreateData, CreateFeedbackMutation, } from CreateFeedbackMutation.graphql; const {commitMutation, graphql} require(react-relay); function commitCreateFeedbackMutation( environment: Environment, input: FeedbackCreateData, ) { return commitMutationFeedbackCreateData(environment, { mutation: graphql mutation CreateFeedbackMutation($input: FeedbackCreateData!) { feedback_create(input: $input) { feedback { id # Step 1: 在 mutation 响应中展开 updatable 片段 ...CreateFeedback_updatable_feedback } } } , variables: {input}, // Step 2: 定义 updater updater: (store, response) { // Step 3: 访问并判空 feedback 对象 const feedbackRef response?.feedback_create?.feedback; if (feedbackRef null) { return; } // Step 4: 用 store.readUpdatableFragment 读出可更新的代理对象 const {updatableData} store.readUpdatableFragment( graphql fragment CreateFeedback_updatable_feedback on Feedback updatable { is_new_comment } , feedbackRef ); // Step 5: 直接给代理对象的字段赋值 updatableData.is_new_comment true; }, }); }这段代码的关键事实updater接收两个参数RecordSourceSelectorProxystore 代理与一个可选对象——即 mutation 响应的读取结果。第二个参数的类型是生成文件导出的$data类型的可空版本且只包含 mutation 直接选中的字段不含 fragment 展开出来的字段该 updater 在 mutation 响应写入 store之后执行readUpdatableFragment返回带updatableData字段的代理对象对其字段赋值即记录到 storeupdater 完成后所有受影响组件自动重渲染。替代 APIreadUpdatableQuery如果知道从根Query类型到目标记录之间的路径可以用readUpdatableQuery以 query 形式读写例如在表单提交时更新 viewer 的namecommitLocalUpdate(environment, store { const {updatableData} store.readUpdatableQuery( graphql query NameUpdaterUpdateQuery updatable { viewer { name } } , {} ); const viewer updatableData.viewer; if (viewer ! null) { viewer.name newName; } });优先选择readUpdatableQuery的理由手头没有现成的 fragment reference例如commitLocalUpdate调用与某个组件没有直接关联手头没有能选中目标记录的父记录的 fragment由于 Relay 中一个已知的类型空洞updatable fragment 不能在顶层展开想在 updatable fragment 中使用变量。目前 updatable fragment 复用 query 传入的变量无法用 fragment 局部变量多次调用readUpdatableFragment。修改链接字段assignable片段readUpdatableQuery不仅能改标量字段还能重新指定记录间的链接如把某个用户设为 viewer 的best_friend。要点是引入assignable片段extend type Viewer { best_friend: User, }// 可赋值的片段只能包含单个字段 __typename graphql fragment AssignBestFriendButton_assignable_user on User assignable { __typename } ;该片段必须在赋值来源新 best friend 上和赋值目标updatable query 中 viewer 的best_friend字段下同时展开然后即可安全赋值const environment useRelayEnvironment(); const onClick () { commitLocalUpdate( environment, (store) { const {updatableData} store.readUpdatableQuery( graphql query AssignBestFriendButtonUpdatableQuery updatable { viewer { best_friend { ...AssignableBestFriendButton_assignable_user } } } , {} ); if (data.user ! null updatableData.viewer ! null) { updatableData.viewer.best_friend data.user; } } ); };关于链接字段赋值有一条硬性约束被赋值的链接字段赋值表达式的右侧必须源自只读的 fragment/query/mutation/subscription。也就是说updatableData.foo updatableData.foo是非法的若要把列表追加元素也必须先从只读 fragment 中取出原列表再执行viewer.best_friends existing_list.concat([newBestFriend])。从抽象类型如Node向具体/接口类型字段赋值时还需要用内联 fragment 做类型收窄必要时配合编译器生成的validator导出做运行时校验。这些进阶细节都在 Imperatively modifying linked fields 中。为什么需要类型安全 updaters这套 APITypesafe updaters FAQ 给出了设计动机旧式 updater API 冗长、非类型安全、容易出错还要求开发者只为写 updater 学一套新方法。类型安全 updaters 复用 Relay 已有的 query/fragment/类型收窄习惯用 getter/setterupdatableData.name Godzilla代替记忆各种方法赋值操作底层仍会转成旧 API 调用但获得了静态类型保障。同时需要明确updatable查询/片段的特性它们不会被真正从服务器获取。服务端不认识 updatable query/fragment即使把 updatable fragment 展开在普通 query 里其字段也不会随请求下发想既获取又修改某个字段需要在普通 query/fragment和updatable query/fragment 中同时选中它由此带来几个后果读取 updatable 数据时可能因 store 中缺失而为空updatable query/fragment 中不能展开普通 fragment其生成产物不含 query ID 与 normalization ASTnormalization AST 用于把网络数据写入 storedefer等指令在 updatable 上下文中没有意义、被禁止。乐观更新optimisticResponse与optimisticUpdater很多时候我们不想等服务器响应才反馈用户操作。比如点击Like后希望立即展示点赞效果——在假设 mutation 必然成功的前提下先把数据写入 store这就是乐观更新若最终失败则回滚该乐观更新。乐观响应optimisticResponse在UseMutationConfig中加入optimisticResponse字段即可。为了让该字段获得 Flow 类型useMutation必须传入类型参数并且mutation 需带raw_response_type指令mutation LikeButtonMutation($input: FeedbackLikeData!) raw_response_type { feedback_like(data: $input) { feedback { viewer_does_like like_count } } }commitMutation({ variables: {input: {id: data.__id}}, optimisticResponse: { feedback_like: { feedback: { // 虽然 id 未显式选中编译器已为我们补选 id: data.__id, viewer_does_like: !data.viewer_does_like, like_count: data.like_count changeToLikeCount, }, }, }, })调用commitMutation后这份数据会被立即写入 store匹配 id 的记录获得新的viewer_does_like值订阅该字段的组件立刻重渲染。当 mutation 成功或失败时乐观响应会被回滚。注意上面的like_count计算依赖组件中读取到的当前点赞数——为此需要在 fragment 中用required(action: THROW)强制读取viewer_does_like与like_count详见 graphql-mutations 文档 的完整示例。乐观响应存在不少陷阱需谨慎使用乐观响应可以包含整个 query 响应的数据包括 fragment 展开的内容。如果开发者后续在组件 fragment 中新增了字段乐观更新期间这些组件可能拿到不一致或部分的数据由于乐观响应的类型包含所有递归嵌套 fragment 的内容类型可能非常庞大给 mutation 加raw_response_type会拖慢 Relay compiler。另外若乐观响应的新值依赖 store 当前值、且可能有多个乐观响应同时影响该值应改用乐观 updater例如两个乐观响应各把点赞数 1当第一个被回滚时第二个仍然生效store 中的点赞数会保持 2。乐观 updateroptimisticUpdater当乐观数据不在 mutation 选中的字段里、或需要向连接增删边而声明式指令不够用时UseMutationConfig还支持optimisticUpdater允许在乐观阶段命令式改写 store——与普通updater的写法一致详见 Imperatively updating store data。updater 系列的执行顺序无论成功失败mutation 涉及的更新按以下顺序执行若提供了optimisticResponse其数据先写入 store若提供了optimisticUpdaterRelay 执行它并按结果更新 store若提供了optimisticResponse对乐观响应处理 mutation 中的声明式指令若 mutation 请求成功回滚此前应用的乐观更新把服务器响应写入 store若提供updater执行之服务器 payload 作为 store 中的一个根字段可供其读取用服务器响应处理声明式指令调用onCompleted。若 mutation 请求失败回滚此前应用的乐观更新调用onError。更新连接Connections增删边与连接标识渲染连接如评论列表时通常还需要在用户操作后向连接增加或移除条目。由于 store 中带connection指令的连接字段以特殊记录保存并累积了该连接所有已获取条目操作它们需要先取得连接记录。完整指南见 Updating Connections实现位于 packages/relay-runtime/handlers/connection/。三种方式获取连接记录// 方式一查询连接的 __id 字段该字段由 Relay 自动附加服务端无需暴露 const connectionID fragmentData?.comments?.__id; // 方式二由父记录 ID 连接 key 计算连接 ID const connectionID ConnectionHandler.getConnectionID( storyID, StoryComponent_story_comments_connection, ); // 方式三由父记录对象直接取连接记录 const storyRecord store.get(storyID); const connectionRecord ConnectionHandler.getConnection( storyRecord, StoryComponent_story_comments_connection, );添加边声明式方式在 mutation/subscription 返回 edge 或 node 的字段上使用appendEdge/prependEdge/appendNode/prependNode也适用于 query并通过connections变量传入连接 ID 数组commitMutationAppendCommentMutation(environment, { mutation: graphql mutation AppendCommentMutation( $connections: [ID!]! $input: CommentCreateInput ) { commentCreate(input: $input) { feedbackCommentEdge appendEdge(connections: $connections) { cursor node { id } } } } , variables: { input, connections: [connectionID], }, });手工方式在 updater 中先用store.getRootField(comment_create)读取 mutation payload 根字段再用ConnectionHandler.buildConnectionEdge(store, connectionRecord, serverEdge)构造边最后用ConnectionHandler.insertEdgeAfter/insertEdgeBefore插入这两个 API 会原地修改连接。若要从零创建边则用store.create创建本地记录 ConnectionHandler.createEdge(store, connectionRecord, newCommentRecord, CommentEdge)。删除边声明式在返回被删节点 IDID或[ID]的字段上加deleteEdge(connections: $connections)手工ConnectionHandler.deleteNode(connectionRecord, commentIDToDelete)它会根据节点 ID 找到对应边并移除。无论用哪种方式修改连接渲染该连接的所有 fragment/query 组件都会收到通知并以最新连接状态重渲染。带过滤条件的连接标识如果连接字段带过滤参数如order_by、filter_mode、language这些过滤值会参与连接标识——分页参数first/last/before/after除外。也就是说过滤值的每种组合都会生成不同的连接记录。此时ConnectionHandler.getConnection需要传入第三个参数ConnectionHandler.getConnection( storyRecord, StoryComponent_story_comments_connection, {order_by: *DATE_ADDED*, filter_mode: null, language: null} );当过滤组合多到难以管理时可以在connection中显式指定参与标识的filters数组comments( order_by: $orderBy filter_mode: $filterMode language: $language ) connection( key: StoryComponent_story_comments_connection filters: [order_by, filter_mode] ) { edges { nodes { body { text } } } }这样只有order_by与filter_mode影响连接身份language变化不会产生新连接记录——前提是它确实不改变服务端返回的条目集合或排序或该参数在应用中永不变化。纯本地数据更新commitLocalUpdate与commitPayload有些更新完全不涉及服务端操作。Relay 为此提供了两个 API详见 Local Data Updates它们既可以作用于客户端专属数据也可以作用于从服务端取回的普通数据。commitLocalUpdate接受一个 environment 与一个 updater 函数const {commitLocalUpdate, graphql} require(react-relay); function commitCommentCreateLocally( environment: Environment, feedbackID: string, ) { return commitLocalUpdate(environment, store { // 在这里命令式地修改 store }); }updater 的store参数是RecordSourceSelectorProxy实例完整 API 见 api-reference/store可以命令式读写 store创建全新记录、更新或删除既有记录与 mutation/subscription 的 updater 不同commitLocalUpdate的 updater不接受第二个参数——因为它没有关联的网络响应本地更新会自动通知订阅了相关数据的组件并触发重渲染。commitPayload接受一个OperationDescriptor与 query 的 payload将其写入 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(operationDescriptor, payload);OperationDescriptor由createOperationDescriptor(query, variables)创建payload 的类型来自给 query 加raw_response_type指令后生成的 Flow 类型同样地任何本地更新都会自动通知订阅组件并触发重渲染。客户端专属数据Client Schema ExtensionsRelay 允许在浏览器端扩展 GraphQL schema用来建模只在客户端创建/读取/更新的数据——例如给服务端取回的数据附加小块本地信息或完整建模一段客户端专属状态。详见 Client-only data。扩展既有类型extend type Comment { is_new_comment: Boolean }新增客户端专属类型同一文件可定义多个类型且可引用服务端类型enum FetchStatus { FETCHED PENDING ERRORED } type FetchState { status: FetchStatus started_by: User! } extend type Item { fetch_state: FetchState }读取客户端专属数据与读取普通字段完全一致在 fragment/query 中直接选中更新它们则可以在 mutation/subscription 的 updater 中完成或使用上文介绍的本地更新原语commitLocalUpdate/commitPayload。GraphQL Subscriptions订阅服务端事件流除了 mutation 这种客户端主动发起的写入Relay 还支持通过GraphQL subscriptions响应服务端事件流。完整指南见 GraphQL subscriptions。声明订阅订阅与 query 类似只是使用subscription关键字subscription FeedbackLikeSubscription($input: FeedbackLikeSubscribeData!) { feedback_like_subscribe(data: $input) { feedback { like_count } } }feedback_like_subscribe是subscription 根字段负责在后台建立订阅与 mutation 类似订阅分两步处理先发生服务端事件再执行查询注意事件流可以与所选字段完全无关——不保证每次通知所选值都发生了变化。使用useSubscriptionconst {graphql, useSubscription} require(react-relay); const {useMemo} require(React); function useFeedbackSubscription(input: FeedbackLikeSubscribeData) { const config useMemo(() ({ subscription: graphql subscription FeedbackLikeSubscription($input: FeedbackLikeSubscribeData!) { feedback_like_subscribe(data: $input) { feedback { like_count } } } , variables: {input}, }), [input]); return useSubscription(config); }要点useSubscription接受一个GraphQLSubscriptionConfig对象包含subscription订阅字面量与variables建立订阅所用的变量同样建议传入 Flow 类型参数以获得静态类型与useLazyLoadQuery不同Relay 不会在渲染阶段建立订阅订阅在 hook 提交后建立每次事件到达Relay 按id匹配 store 中的记录并更新相关组件自动重渲染。⚠️ 传给useSubscription的GraphQLSubscriptionConfig必须被 memoized如上例用useMemo否则useSubscription会在每次渲染时销毁并重建订阅GraphQLSubscriptionConfig还支持回调字段onNext收到 payload 时、onError出错时、onCompleted服务端结束订阅时。声明式指令deleteRecord、连接增删指令同样适用于订阅。订阅需要网络层支持通常在 network-layer 指南 中配置 WebSocket 传输如graphql-ws并把subscribe函数通过Network.create(fetchQuery, subscribe)注入。在 mutation 期间失效数据Staleness执行 mutation 的推荐做法是在 mutation 体内把受影响的数据全部请求回来让本地 store 与服务端保持一致。但某些涟漪效应巨大的 mutation如屏蔽用户退出群组很难枚举全部受影响数据。此时更直接的做法是显式标记部分数据或整个 store为过期让 Relay 在下次渲染时自动 refetch。相关 API 见 Staleness of Data 章节。小结Relay 的数据更新体系围绕本地 store 与服务端保持一致这一核心目标设计形成了一条清晰的能力阶梯简单场景优先用声明式手段——mutation 响应自动归一化写入 store、deleteRecord删除记录、appendEdge等指令操作连接中等场景用updater/optimisticResponse在响应写入前后微调 store控制执行时机与回滚语义复杂场景用类型安全的readUpdatableQuery/readUpdatableFragmentupdatable与assignable片段精确改写标量字段与链接关系纯本地状态用commitLocalUpdate/commitPayload与 client schema extensions 建模客户端专属数据服务端推送用useSubscription订阅事件流配合 WebSocket 网络层。无论走哪条路径最终效果都是一致的store 中的记录被更新订阅了相关字段的组件自动重渲染——这正是 Relay局部写、全局一致的数据驱动哲学在写入侧的体现。若要继续深入推荐按顺序阅读 GraphQL mutations、命令式修改 store 数据 与 更新连接 三篇文档并结合 useMutation 源码 与 ConnectionHandler 实现 理解底层行为。赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐Relay GraphQL Mutations 完全指南从 useMutation 到乐观更新与 Store 数据同步Relay GraphQL Mutations 完全指南从 useMutation 到乐观更新与 Store 数据同步 本指南基于仓库中的官方文档 graph前端开发工具Relay 数据更新指南Mutation、Subscription 与本地 Store 命令式修改Relay 数据更新指南Mutation、Subscription 与本地 Store 命令式修改 本篇技术指南以 RelayRelay is a Java前端开发工具Relay 18 类型安全 Store 更新指南使用 readUpdatableQuery / readUpdatableFragment 命令式修改 Store 数据Relay 18 类型安全 Store 更新指南使用 readUpdatableQuery / readUpdatableFragment 命令式修改 Sto前端开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表