前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载本文聚焦 Relay 中一个关键的类型——SelectorStoreUpdater。它是以commitMutation、useMutation、requestSubscription等 API 执行 mutation / subscription 时用于命令式读写 Relay Store 的更新函数签名固定为(store: RecordSourceSelectorProxy, data) void第一个参数让你直接创建、修改或删除 store 中的记录第二个参数携带本次操作的响应数据。读完本文你将掌握SelectorStoreUpdater的类型定义、RecordSourceSelectorProxy的全部可用 API、它在各 API 中的接入位置并能写出真实可用的 updater 与 optimisticUpdater 代码。类型定义签名与语义在 RelayStoreTypes.js 中SelectorStoreUpdater被精确定义为export type SelectorStoreUpdaterin TMutationResponse ( store: RecordSourceSelectorProxy, data: ?TMutationResponse, ) void;它有两个参数参数类型含义storeRecordSourceSelectorProxy绑定到当前操作 selector 的 store 代理用于命令式读写 Relay Storedata?TMutationResponse本次 mutation / subscription 的响应对象payload类型由具体操作生成订阅首次建立或收到新载荷时均可为undefined注意两点细节泛型in TMutationResponse声明为**逆变contravariant**位置意味着 updater 可以接受该操作响应类型或其父类型的值配合 Relay 的代码生成类型data字段可获得完整的类型推导。data可为null?修饰响应载荷未就绪例如订阅刚建立、或 optimistic 阶段时传入undefined。因此 updater 内必须对data做存在性判断不能假设它总是存在。与 StoreUpdater 的区别同文件 RelayStoreTypes.js 还定义了更基础的StoreUpdaterexport type StoreUpdater (store: RecordSourceProxy) void;两者的核心区别在于两点代理能力不同StoreUpdater只能拿到RecordSourceProxy只有create/delete/get/getRoot/invalidateStore/readUpdatableQuery/readUpdatableFragment而SelectorStoreUpdater拿到的是RecordSourceSelectorProxy它扩展了RecordSourceProxy额外提供getRootField(fieldName)与getPluralRootField(fieldName)可以精确取到本次 query / mutation / subscription 文档中的根字段。载荷参数不同SelectorStoreUpdater多了一个data参数直接携带响应载荷无需再从 store 中反查。从源码注释看RelayStoreTypes.jsSelectorStoreUpdater被描述为“StoreUpdater的进阶版接受绑定到特定 selector 的代理以方便访问 query/mutation 的根字段并接收 mutation 的响应对象作为第二参数”。RecordSourceSelectorProxystore 参数的完整 APISelectorStoreUpdater的第一个参数类型RecordSourceSelectorProxy的完整接口定义在 RelayStoreTypes.js官方文档位于 store.md行为说明如下interface RecordSourceSelectorProxy { create(dataID: string, typeName: string): RecordProxy; delete(dataID: string): void; get(dataID: string): ?RecordProxy; getRoot(): RecordProxy; getRootField(fieldName: string): ?RecordProxy; getPluralRootField(fieldName: string): ?Array?RecordProxy; invalidateStore(): void; }各方法的作用create(dataID, typeName): RecordProxy—— 在 store 中新建一条记录dataID为客户端 ID通常用client:xxx形式如client:newTodotypeName为 GraphQL schema 中的类型名如Todo。返回RecordProxy供后续写入字段。delete(dataID): void—— 删除指定 ID 的记录。删除后原本指向该记录的边字段在读取时默认返回undefined。get(dataID): ?RecordProxy—— 按 ID 取回已有记录不存在时返回null。getRoot(): RecordProxy—— 返回 GraphQL 文档根节点root query对应的记录代理。getRootField(fieldName): ?RecordProxy—— 返回当前操作文档中指定根字段对应的记录代理。这是 updater 中最常用的入口之一例如store.getRootField(createTodo)。getPluralRootField(fieldName): ?Array?RecordProxy—— 返回集合型根字段如node(id: $id)、nodes(first: 10)对应的记录代理数组。invalidateStore(): void—— 将整个 store 标记为失效触发所有订阅者重新读取一般用于需要整体刷新数据源的场景。写字段RecordProxy 的 setter/getter通过上述方法拿到的RecordProxy接口见 RelayStoreTypes.js提供字段级读写getValue(name, args?)/setValue(value, name, args?)—— 读写标量字段字符串、数字、布尔、枚举、null。getLinkedRecord(name, args?)/setLinkedRecord(record, name, args?)—— 读写单个关联对象linked recordargs用于带参数字段的 storage key 计算。getLinkedRecords(name, args?)/setLinkedRecords(records, name, args?)—— 读写关联对象数组plural 字段。getOrCreateLinkedRecord(name, typeName, args?)—— 若关联对象已存在则取回否则创建新记录并挂到该字段。copyFieldsFrom(source)—— 从另一个 record 拷贝全部字段。getDataID()/getType()—— 获取当前记录 ID 与类型名。invalidateRecord()—— 标记单条记录失效。字段参数args的作用Relay 通过name与args共同计算字段的 storage key见 RelayStoreUtils.js 中的getStorageKey。例如comments(first: 10)与comments(first: 20)是两个不同的存储槽位读/写时必须传入一致的参数才能命中同一份数据。SelectorStoreUpdater 在 Relay 公开 API 中的接入位置SelectorStoreUpdater并不是孤立类型它在 Relay 的多条对外 API 中以updater/optimisticUpdater字段出现1. MutationConfigcommitMutation / useMutation文档 MutationConfig.md 中updater与optimisticUpdater都声明为SelectorStoreUpdaterupdater服务器响应写入 store之后执行用于根据真实响应调整 store例如把新创建记录的边接上列表。optimisticUpdatercommitMutation被调用时、optimisticResponse被归一化进 store之后立即执行用于先行在 store 中模拟最终状态例如先把新建项插进列表让 UI 立即反馈。2. GraphQLSubscriptionConfigrequestSubscription / useSubscription文档 GraphQLSubscriptionConfig.md 中updater同样是SelectorStoreUpdater用于在每次订阅载荷到达并归一化后更新 store例如把新增的评论插入评论列表。3. Environment 与 PublishQueue 内部IEnvironment.executeSubscription与executeMutation的配置项RelayStoreTypes.js接受updaterExecuteMutationConfigRelayStoreTypes.js同时暴露optimisticUpdater与updaterPublishQueue.commitPayloadRelayStoreTypes.js在提交载荷时挂载 updater而applyUpdate/revertUpdate则管理 optimistic 更新RelayStoreTypes.js。也就是说凡是 mutation 或 subscription 的写路径最终都会汇聚到SelectorStoreUpdater。完整实战示例下面以创建 Todo的 mutation 为例展示optimisticUpdater与updater的典型写法。GraphQL mutationmutation TodoCreateMutation($input: TodoCreateInput!) { todoCreate(input: $input) { todo { id text complete } } }定义 updater 并接入 useMutationimport {useMutation} from react-relay; const [commit, isInFlight] useMutation(graphql mutation TodoCreateMutation($input: TodoCreateInput!) { todoCreate(input: $input) { todo { id text complete } } } ); const updateTodoList (store, data) { // 1. 从响应载荷中取出新建的 todo const newTodo store.getRootField(todoCreate).getLinkedRecord(todo); if (!newTodo) { return; } // 2. 取回根查询找到待追加的列表边listKey 对应 connection 配置 const root store.getRoot(); const connection ConnectionHandler.getConnection( root, TodoList_todos, ); if (connection) { // 3. 构造新边并追加到列表头部 const edge ConnectionHandler.createEdge( store, connection, newTodo, TodoEdge, ); ConnectionHandler.insertEdgeBefore(connection, edge); } }; const commitCreate (input) { commit({ variables: {input}, // optimisticUpdater请求发出前先在 store 中插入一条占位数据 optimisticUpdater: (store) { // data 参数在乐观阶段为 undefined因此用 client ID 手工建记录 const clientId client:newTodo: Date.now(); const newTodo store.create(clientId, Todo); newTodo.setValue(input.text, text); newTodo.setValue(false, complete); const root store.getRoot(); const connection ConnectionHandler.getConnection(root, TodoList_todos); if (connection) { const edge ConnectionHandler.createEdge(store, connection, newTodo, TodoEdge); ConnectionHandler.insertEdgeBefore(connection, edge); } }, // updater真实响应到达后把占位记录替换为服务器返回的真实记录 updater: updateTodoList, onCompleted: () console.log(created), onError: (error) console.error(error), }); };要点乐观阶段optimisticUpdater拿不到data为undefined必须用store.create(clientId, Todo)手工创建占位记录并写入本地字段值。响应阶段updater能拿到真实的data此时优先从getRootField(todoCreate)读取服务器数据再用getLinkedRecord(todo)取新记录占位记录会在真实记录写入后自然被取代同一 clientID 会被服务器 ID 覆盖。Connection 辅助列表操作推荐使用ConnectionHandlercreateEdge/insertEdgeBefore/deleteEdge等它封装了边记录与链表指针的维护避免手写底层指针字段。订阅场景示例import {requestSubscription} from relay-runtime; requestSubscription(environment, { subscription: graphql subscription TodoCommentSubscription($todoID: ID!) { commentAdded(todoID: $todoID) { comment { id text author { name } } } } , variables: {todoID}, // 每次收到新评论载荷后调用 updater: (store, data) { const commentAdded store.getRootField(commentAdded); const newComment commentAdded commentAdded.getLinkedRecord(comment); if (!newComment) { return; } const todo store.get(todoID); const connection todo ConnectionHandler.getConnection(todo, Todo_comments); if (connection) { const edge ConnectionHandler.createEdge(store, connection, newComment, CommentEdge); ConnectionHandler.insertEdgeBefore(connection, edge); } }, onError: (error) console.error(error), });底层实现RelayRecordSourceSelectorProxy 做了什么store参数在运行时是RelayRecordSourceSelectorProxy类的实例源码见 RelayRecordSourceSelectorProxy.js。它包装了一个RelayRecordSourceMutator与一个底层RecordSourceProxy并持有当前操作的SingularReaderSelectorgetRootField(fieldName)的实现RelayRecordSourceSelectorProxy.js先在selector.node.selections中按字段名查找LinkedField兼容RequiredField只返回当前操作文档中确实声明过的根字段找不到返回null。因此getRootField的参数必须是本次 mutation/subscription 文档里真实存在的根字段名。getOperationRoot()会取 selector 的dataID对应的记录不存在则按ROOT_TYPE创建RelayRecordSourceSelectorProxy.js。所有set*写入都会落到RelayRecordSourceMutator它维护变更集changeset语义写入不会立刻破坏现有快照而是与乐观更新、回滚机制协同最终由RelayPublishQueue.run()统一发布并通知订阅者。从调用链看commitMutation在 commitMutation.js 中会把optimisticUpdater与updater一并传给executeMutation若配置了configs声明式配置还会经RelayDeclarativeMutationConfig.convert把声明式指令如appendEdge、deleteRecord转换并合并进这两个 updater。这意味着声明式配置与命令式 updater 可以共存两者按顺序作用于同一 store当声明式指令无法表达复杂业务逻辑时如需要条件判断、多重嵌套查找改用命令式SelectorStoreUpdater是标准解法。最佳实践与注意事项善用data但不要依赖它做唯一数据源data可能为undefined乐观阶段、订阅刚建立。安全写法是先取data取不到就返回或降级涉及新建记录的列表插入最终以服务器 ID 覆盖 client ID 为准。优先getRootField避免手工重建参数RelayRecordSourceSelectorProxy的注释RelayRecordSourceSelectorProxy.js明确指出根字段常带复杂参数手动用getRoot().getLinkedRecord()重建参数集合非常繁琐getRootField直接复用操作文档中的字段定义即可。列表字段用 ConnectionHandler直接写setLinkedRecords会丢失 connection 的边/指针结构ConnectionHandler.getConnection、createEdge、insertEdgeBefore、deleteEdge是维护分页列表的标准工具。更新不可变数据链路updater 运行于归一化之后的发布队列中写入的记录会触发 store 通知相关 fragment 自动重渲染。不要手动触发任何 setState 或外部刷新。区分updater与optimisticUpdater的时机前者在服务器响应后执行最终态后者在请求发出前执行中间态。乐观更新在请求失败/被取消时会自动回滚因此乐观阶段的写入不需要手动撤销。类型安全SelectorStoreUpdaterTMutationResponse的data参数会由 Relay 生成的类型自动推断建议把 updater 提取为具名函数并显式标注类型便于复用与单测。小结SelectorStoreUpdater是 Relay 数据写路径的最后一公里通过绑定 selector 的RecordSourceSelectorProxy你可以在 mutation 与 subscription 的任意阶段精确创建、更新、删除 store 记录并与声明式 configs、ConnectionHandler 等机制组合。其类型定义见 RelayStoreTypes.js完整 store API 文档见 store.md接入示例可继续阅读 commit-mutation.md 与 request-subscription.md。赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐如何把 axios 接入 Tauri 的原生 fetch 以绕过浏览器 CORS 限制如何把 axios 接入 Tauri 的原生 fetch 以绕过浏览器 CORS 限制 在 Tauri 应用里前端代码运行在系统 webview 中跨域请求前端开发工具Relay SelectorStoreUpdater 类型详解在 Mutation 与 Subscription 中命令式读写 Relay StoreRelay SelectorStoreUpdater 类型详解在 Mutation 与 Subscription 中命令式读写 Relay Store Sel前端开发工具Relay 中 SelectorStoreUpdater 类型完全指南在 updater 函数中命令式读写 StoreRelay 中 SelectorStoreUpdater 类型完全指南在 updater 函数中命令式读写 Store 在 RelayReact 的数据驱动前端开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
