前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载本篇指南聚焦 Relay 数据更新机制中一个关键能力在 updater 函数内以命令式imperative方式直接读写 Relay Store从而完成声明式指令和普通网络响应无法覆盖的复杂本地数据更新。你将掌握 updater 的适用场景与禁用场景、optimisticUpdater与updater的执行时机差异、基于RecordSourceSelectorProxy/ConnectionHandler的完整可运行示例以及 v19 中与之对应的 typesafe 替代方案能够直接在你的 Relay 应用中落地。updater 是什么在 Relay Store 上获得“完全控制权”Relay 在内存中维护一份归一化normalized的 GraphQL 数据 Store所有记录record按dataID存储。通常情况下数据流向是网络响应 → 归一化 → 写入 Store → 订阅了对应数据的组件收到通知并重渲染这一过程由 Relay 自动完成。但 Relay 也开放了一条命令式通道在 updater 函数中你可以直接读取和写入 Store 数据。updater 接收的第一个参数store是RecordSourceSelectorProxy实例该接口允许你对 Store 进行完全的命令式操作——你可以创建全新的记录、更新已有记录、删除记录从而以编程方式精确控制“收到变更响应后 Store 如何变化”。这一点在 Store API 参考中有明确描述Data in Relay stores can be imperatively modified within updater functions.什么时候应该使用 updater复杂客户端更新当本地数据的变更比“直接把网络响应写入 Store”更复杂且无法由声明式 mutation 指令如appendEdge、deleteEdge、deleteRecord处理时就应该提供 updater 函数。例如一次 mutation 返回后需要同时更新多个互不关联的记录、需要依据 Store 中已有值做条件判断、或者需要把响应数据重新组织后再写入。客户端 Schema 扩展Client Schema Extensions字段的初始化网络响应必然不会包含在客户端 schema 扩展例如extend type Feedback { is_new_comment: Boolean }中定义的字段数据。因此当这类字段需要随 mutation 一起初始化时updater 是最直接的选择——你可以在响应写入 Store 后立即为扩展字段写入合适的初始值。这也是 新版 typesafe updater 指南中第一个示例的动机。其他 API 的不可替代性还有一部分操作只能通过 updater 完成例如失效invalidate节点调用record.invalidateRecord()或store.invalidateStore()删除节点调用store.delete(dataID)查找某个字段下的所有连接connection借助ConnectionHandler在给定字段上定位连接记录。这些能力都没有对应的声明式替代品属于 updater 的“独占领地”。多个乐观响应修改同一个 Store 值时的行为这是一个重要的边界情况如果两个乐观响应optimistic responses影响了同一个值而第一个乐观响应被回滚第二个依然保持生效。举例两个乐观响应各自把某条 story 的点赞数likeCount增加 1。当第一个乐观响应被回滚时第二个乐观响应仍然生效由于第二个乐观响应不会被重新计算点赞数最终会停留在“增加了 2”的状态而不是恢复为“增加了 1”。与之相对乐观 updateroptimistic updater在这种情况下会被重新执行。因此如果乐观更新的值依赖于 Store 中已有的其他值、且存在多个乐观响应可能同时影响该值官方建议使用乐观 updater 而非纯optimisticResponse参见 graphql-mutations.md 中关于乐观更新的讨论。什么时候不应该使用 updater触发其他副作用updater 是纯 Store 变更逻辑不应用于触发副作用side effects如弹出提示、跳转路由、埋点上报。这类逻辑应放在onCompleted回调中——该回调保证在 mutation 完成后恰好调用一次而 updater / optimistic updater 可能被重复调用例如乐观 updater 在 mutation 失败回滚后不再执行、但在重试等场景下可能再次进入相关流程不具备“恰好一次”的语义保证。updater 函数的各种类型与入口 API不同的入口 API 支持不同的 updater 字段需要区分清楚API支持的 updater 字段说明useMutation/commitMutationoptimisticUpdater、updatermutation 场景两类都支持requestSubscription/useSubscriptionupdater订阅场景仅支持常规 updatercommitLocalUpdateupdater 函数参数只有一个store本地数据更新无网络 payload详见 local-data-updates.md从类型定义看useMutation和commitMutation接受的配置对象即MutationConfig其 MutationConfig.md 中明确了两个字段optimisticUpdater类型为SelectorStoreUpdater在commitMutation被调用、且optimisticResponse已归一化写入 Store 之后执行updater类型为SelectorStoreUpdater在收到服务端 payload、且该 payload 已写入 Store 之后执行。而 SelectorStoreUpdater.md 给出了函数签名(store: RecordSourceSelectorProxy, data) void并明确指出该接口允许“命令式地直接读写 Relay Store”可以创建全新记录也可以更新或删除已有记录。乐观 updater vs 常规 updater执行时机与完整顺序Mutation 可以同时携带乐观 updater 和常规 updater二者的语义截然不同乐观 updateroptimistic updater在 mutation 被触发时立即执行。当 mutation完成或失败时乐观更新被回滚随后服务端响应被写入 Store常规 updater 执行。常规 updaterupdater在 mutation成功完成后执行。graphql-mutations.md 给出了完整的执行顺序这里完整列出如果提供了optimisticResponse先将该数据写入 Store如果提供了optimisticUpdaterRelay 执行它并更新 Store如果提供了optimisticResponse处理 mutation 中的声明式 mutation 指令作用于乐观响应mutation 请求成功时回滚已应用的乐观更新将服务端响应写入 Store如果提供了updaterRelay 执行它并更新 Store——此时服务端 payload 在 Store 中以**根字段root field**形式可供 updater 读取使用服务端响应处理声明式 mutation 指令调用onCompleted回调。mutation 请求失败时回滚已应用的乐观更新调用onError回调。一个值得注意的细节来自 MutationConfig.mdonCompleted收到的是“在 updater 和声明式指令应用之后从 Store 读出的 mutation 片段”因此被deleteRecord删除的记录在其中可能为 null。实战示例为commitMutation提供 updater 添加评论下面是一个完整的可运行示例评论创建成功后将服务端返回的新 edge 插入到本地已有的评论连接中。它完整保留了原文档的代码骨架并补充了类型与注释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}, updater: (store: RecordSourceSelectorProxy, _response: ?CreateCommentMutation$data) { // 本例未使用 _response但它被提供且是静态类型化的 // 1. 获取父记录Feedback const feedbackRecord store.get(feedbackID); // 2. 通过连接 key 获取连接记录 const connectionRecord ConnectionHandler.getConnection( feedbackRecord, CommentsComponent_comments_connection, ); // 3. 获取服务端返回的 payloadmutation 根字段 const payload store.getRootField(comment_create); // 4. 获取 payload 内的 edge const serverEdge payload.getLinkedRecord(comment_edge); // 5. 基于服务端 edge 构建可插入连接的 edge const newEdge ConnectionHandler.buildConnectionEdge( store, connectionRecord, serverEdge, ); // 6. 将 edge 追加到连接末尾 ConnectionHandler.insertEdgeAfter( connectionRecord, newEdge, ); }, }); } module.exports {commit: commitCommentCreateMutation};逐点剖析这个示例究竟做了什么store参数是RecordSourceSelectorProxy实例。这是 updater 的命令式读写入口让你在响应 mutation 时对 Store 拥有完全控制可以创建全新记录、更新已有记录、删除记录。第二个data参数包含 mutation 片段直接选中的数据可用于不经过store直接取 payload。其类型从自动生成的Mutation.graphql.js文件中导入命名为MutationName$data。需要注意两点该data参数的类型是$data类型的可空版本data只包含 mutation直接选中的字段——如果 mutation 中展开了其他 fragment那些 fragment 的数据默认不会出现在data里这正是 legacy updater 容易踩的坑之一也是 typesafe updater 通过updatablefragment 解决的痛点。本例的实质是“向连接添加新项”在服务端成功添加评论后把新评论插入本地连接。连接的增删改细节可参考 updating-connections.md。特别地本例其实不需要手写 updater——这是使用appendEdge指令的理想场景mutation 响应是 Store 中的一个根字段记录可通过store.getRootField读取。此处读取的是comment_create即 mutation 响应中的根字段。mutation 的 root 与 query 的 root 不同在 mutation updater 中store.getRootField只能取得 mutation 响应中的记录若要访问不在 mutation 响应中的根级数据应改用store.getRoot().getLinkedRecord。updater 完成后自动触发重渲染updater 产生的本地数据变更会自动通知订阅了这些数据的组件并触发重渲染无需手动触发。ConnectionHandler 的源码级原理示例中使用的ConnectionHandler位于 packages/relay-runtime/handlers/connection/ConnectionHandler.js理解其内部实现有助于把握连接更新的本质getConnection(record, key, filters?)内部通过getRelayHandleKey(connection, key, null)计算出 handleKey然后调用record.getLinkedRecord(handleKey, filters)返回连接记录。也就是说connection指令声明的连接在 Store 中实际上是以父记录上的一个“handle 字段”存储的参见 ConnectionHandler.js#L283-L290。getConnectionID(recordID, key, filters?)用getStableStorageKey(handleKey, filters)生成稳定的存储 key再通过generateClientID(recordID, storageKey)计算连接记录的 ID——这就是为什么带不同过滤参数的同名连接会得到不同的连接 ID详见 ConnectionHandler.js#L323-L331。buildConnectionEdge(store, connection, edge)为服务端返回的 edge 创建一份带唯一 ID的副本。它利用连接记录上的__connection_next_edge_index计数器生成基于索引的客户端 ID避免同一连接重复拉取时 edge ID 冲突详见 ConnectionHandler.js#L549-L576。insertEdgeAfter(record, newEdge, cursor?)无cursor时直接把新 edge 追加到edges数组末尾有cursor时遍历现有 edges找到 cursor 匹配的 edge 后插入其后找不到则仍追加到末尾详见 ConnectionHandler.js#L367-L402。insertEdgeBefore是对称的前插实现。store参数的核心 API 速查RecordSourceSelectorProxy的完整接口定义见 store.md核心方法如下interface RecordSourceSelectorProxy { create(dataID: string, typeName: string): RecordProxy; // 创建新记录 delete(dataID: string): void; // 删除记录 get(dataID: string): ?RecordProxy; // 按 ID 取记录 getRoot(): RecordProxy; // 取文档根query 根 getRootField(fieldName: string): ?RecordProxy; // 取 mutation 响应根字段 getPluralRootField(fieldName: string): ?Array?RecordProxy; // 取复数根字段 invalidateStore(): void; // 全局失效 Store }返回的RecordProxy则提供了读写记录字段的能力getValue/setValue标量字段、getLinkedRecord/setLinkedRecord单值关联字段、getLinkedRecords/setLinkedRecords列表关联字段、getOrCreateLinkedRecord不存在则创建、copyFieldsFrom字段拷贝、getDataID、getType、invalidateRecord等。完整的逐方法说明与示例代码都在 store.md 中。声明式指令多数场景下的现代替代方案在投入手写 updater 之前值得先确认声明式 mutation 指令能否覆盖需求。Relay 提供了appendEdge、prependEdge、appendNode、prependNode和deleteEdge等指令它们可以直接作用于 mutation / subscription / query 响应中的字段把新 edge 追加/前插到指定连接、或按节点 ID 删除 edge从而免去手写ConnectionHandler样板代码详见 updating-connections.md。但这些指令提供的控制力有限无法覆盖所有用例例如需要按条件决定是否插入某条连接如“仅当评论来自好友时才插入好友专属连接”、需要同时维护多个不同过滤参数下的连接记录、或需要对 Store 做与连接无关的更复杂变更。此时手写 updater 仍是正确选择。关于“连接身份connection identity”与过滤参数的关系——每个非分页过滤参数的取值组合都会产生独立的连接记录ConnectionHandler.getConnection需传入第三个参数filters来定位——详见 updating-connections.md。从 legacy 到 typesafev19 中的升级路径需要说明的是本指南对应的 legacy 文档标题即带有(unsafe)后缀slug 为imperatively-modifying-store-data-unsafe。v19 同时提供了typesafe updater方案见 imperatively-modifying-store-data.md核心差异在于在 mutation 响应中展开带updatable指令的 fragment然后调用store.readUpdatableFragment(fragment, fragmentReference)拿到类型安全的updatableData代理对象直接对字段赋值如updatableData.is_new_comment true即可或者已知从根到目标记录的路径时用store.readUpdatableQuery(query, variables)完成同样操作如修改viewer.name这些 API 也出现在RecordSourceProxy的接口中见 store.md且比手写setValue更具类型安全性。readUpdatableQuery还有两个 legacy 方案没有的优势不依赖手头有 fragment reference例如commitLocalUpdate与组件无明确关联时且可以规避 Relay 的一个已知类型漏洞——updatablefragment 无法在顶层展开。需要给 updatable fragment 传 fragment 局部变量时readUpdatableFragment目前也受限updatable fragment 会复用 query 的变量这些取舍细节都记录在 imperatively-modifying-store-data.md。总结updater 函数是 Relay 数据更新体系中最底层、最灵活的一环它把 Store 的命令式读写权交给你用来完成复杂客户端更新、初始化客户端 schema 扩展字段、以及失效/删除/连接查找等独占操作。使用时要牢记三条边界副作用交给onCompleted、乐观更新优先考虑可重算的 optimistic updater、能被appendEdge等声明式指令覆盖的场景不必手写 updater。理解RecordSourceSelectorProxy与ConnectionHandler的实现细节连接记录实为父记录上的 handle 字段、连接 ID 由 key filters 稳定生成、edge 副本依赖自增索引避免 ID 冲突能帮你写出正确、高效且可维护的 Store 更新逻辑。完整 API 请参阅 Store API 参考升级迁移可对照 typesafe updater 指南。赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐Relay 命令式修改 Store 数据updater 函数完整指南基于 Relay v15 的 legacy 用法Relay 命令式修改 Store 数据updater 函数完整指南基于 Relay v15 的 legacy 用法 Relay 将本地数据写入与网络响应前端开发工具Relay 指南在 updater 函数中命令式修改 Store 数据Relay 指南在 updater 函数中命令式修改 Store 数据 Relay 采用归一化normalized的本地内存 Store 来缓存 Grap前端开发工具Relay 16 数据更新指南在 updater 中命令式修改 Store 数据legacy 方式Relay 16 数据更新指南在 updater 中命令式修改 Store 数据legacy 方式 Relay 的 store 是应用本地数据的中枢。除了前端开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
从零实现BP神经网络识别手写数字:源码级实战与避坑指南 简介:基于Python实现BP神经网络识别手写字体的完整项目源码,源自作者大三期末高分大作业,经导师指导并获98分评审,适合计算机专业正在准备课程设计、期末大作业的学生,也适合需要项目实战练习的深度学习入门者。压缩包… · 2026/9/24 19:01:12
Java Web图书馆借阅管理系统:从源码部署到答辩全攻略 简介:一份基于 Java Web 的图书馆借阅管理系统完整项目包,适合作为高校软件专业毕业设计、课程设计参考,也面向需要掌握 SSM 框架与 MySQL 整合开发的 Java 开发者。系统按图书管理员、学生用户等角色设计,覆盖图书借阅、书籍分类… · 2026/9/24 19:01:12
基于YOLO的黄瓜害虫检测数据集解读与实战训练指南 简介:黄瓜害虫目标检测数据集是一份面向农业算法工程师、科研人员及农技推广人员的行业级标注数据,专门针对蚜虫、果蝇、南瓜甲虫、潜叶虫、粉虱五类高危害性害虫,适用于智能农业监测、精准施药决策、农业物联网预警及有害生物防治研究等场景… · 2026/9/24 19:01:12
YOLOv5 6.1全中文注释版:从源码解析到树莓派部署实战 简介:YOLOV5 6.1版本全中文注释源码包,面向目标检测初学者、研究生及创新创业大赛参赛团队,针对官方代码结构复杂、英文注释难以理解等痛点,对模型构建、数据集准备、训练验证、推理部署等核心模块逐行添加中文注解,并… · 2026/9/24 20:46:45
SpringBoot+Vue墙绘交易平台:从订单设计到并发控制的全栈实战解析 我直接说结论:如果你现在想找一个既能练手、又能直接拿去生产环境的Java全栈项目,基于SpringBootVue的墙绘产品展示交易平台,是个相当合适的参考系。这个项目把电商交易、内容展示、后台管理三个核心场景串在一起,技术栈又恰好是当… · 2026/9/24 20:46:45
图转PPT技术解析:从OCR到PPTX的完整实现路径 1. 为什么“一键生成PPT”这件事,远没有想象中简单1.1 从一句需求说起:AI生成PPT到底卡在哪“用AI一键生成PPT”这个说法,这两年几乎成了办公效率赛道的标配口号。你在任何一个内容平台搜“AI做PPT”,都能看到大量演示视频&#x… · 2026/9/24 20:46:45
Qt QPainter二维绘制从原理到实战:机制、坐标系与仪表盘实现 在Qt开发里,画图这件事十有八九绕不开QPainter。无论是做自绘控件、数据可视化面板,还是临时画个折线图、仪表盘、地图标注,最终都要落到这个类上。很多人觉得QPainter难,其实是没把它的绘图机制、坐标体系和常用API串起来理解。这… · 2026/9/24 20:46:45
图片转PPT全链路实战:OCR、版面分析与PPTX生成避坑指南 图片转PPT这件事,表面上看是个格式转换的小需求,但真正动手做过的人都知道,坑远比想象中多。我最初接触这个需求,是因为手头有一批纸质培训资料和扫描版的技术文档,需要整理成可编辑的PPT课件。当时想得很简单——图片… · 2026/9/24 20:46:45
基于监督学习的Web入侵检测系统:Python实现与特征工程全解析 简介:高分毕业设计基于监督学习的Web入侵检测系统Python实现在此提供,面向计算机相关专业学生及从业者,可用于课程设计、期末大作业或毕业设计参考。资源共60个文件,压缩包2.25MB,包含18个Jupyter Notebook过程分析、8… · 2026/9/24 20:46:25
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44