Relay 实战:useMutationAction_EXPERIMENTAL 如何捕获顶级字段错误(data: null)
Relay 实战useMutationAction_EXPERIMENTAL 如何捕获顶级字段错误data: null【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relay本篇以 Relay 仓库中的端到端测试 fixtureuseMutationAction-top-level-field-error.md为核心讲解useMutationAction_EXPERIMENTAL在服务端返回{data: null, errors: [...]}例如非空non-null顶级 mutation 字段执行失败时的完整行为链路Relay 为何将其视为致命错误、错误如何从底层网络一路路由到onError与 Promise 的reject以及如何在 React 组件中用startTransitiontry/catch兜住它。读完你可以直接在自己的应用中复刻这套「模拟 mutation 失败并断言错误 UI」的测试模式。场景定义什么是「顶级字段错误data: null」在 GraphQL 中一个 mutation 的响应负载payload可以是{data: {...}}成功字段级错误如列表中某个元素失败通常以data内字段为null外加顶层errors的形式表达{data: null, errors: [...]}整个顶层 mutation 无法产出数据。最典型的原因是顶层字段是非空non-null类型而它的解析器抛错——非空约束的失败会向上冒泡最终把整个data打成null{errors: [...]}且无data网络/传输层失败。本 fixture 针对第二种情况data: null且附带errors。文档标题中的 Top-Level Field Error 指的就是这种「非空顶级字段失败导致整棵数据树为空」的错误形态。Relay 如何判定data: null为致命错误data: null与「字段级错误」有本质区别。字段级错误时data对象仍然存在Relay 可以正常完成归一化normalization与 UI 更新errors仅作为附带信息传给onCompleted的第二个参数。而data: null意味着没有任何数据可以写入 storeRelay 无法完成一次成功的 mutation因此直接将其视为致命fatal错误。这一判断发生在 OperationExecutor.js 中当响应对象的data null时排除仅有extensions的 payload执行器会构造一个RelayNetwork错误并抛出No data returned for operation OperationName, got error(s): errors 中每个 message 以换行拼接 See the error source property for more information.注意该错误对象上还挂了一个source属性携带{errors, operation, variables}三份信息见 OperationExecutor.js供日志与上报工具深挖根因。在测试快照 useMutationAction-top-level-field-error.snap.md 中你可以在最终渲染的 HTML 里直接看到这条错误消息落到 UI 上Caught error: No data returned for operation AppDoSomethingMutation, got error(s): Non-nullable field failed See the error source property for more information.错误如何到达commitAction从onError到 PromiserejectuseMutationAction_EXPERIMENTAL的实现在 useMutationAction_EXPERIMENTAL.js 中它本质上是useMutation的「action 化」变体返回一个异步函数commitAction(variables)其内部通过commitMutation提交变更并把两个回调桥接为 Promise 语义onCompleted(response)→resolve(response)onError(error)→reject(error)。const commitAction useCallback( (variables) { return new Promise((resolve, reject) { commitMutation(environment, { mutation, variables, onCompleted: (response) resolve(response), onError: (error) reject(error), }); }); }, [environment, mutation], );完整配置见 commitMutation.js除了mutation、variables、onCompleted、onError还支持optimisticResponse、optimisticUpdater、updater、cacheConfig、configs声明式 mutation 配置、uploadables等。其中commitMutation在订阅的error事件上直接透传onError因此OperationExecutor抛出的data: null致命错误会沿executeMutation→ 订阅error→onError→reject的链路直达commitAction的调用方。补充commitMutation对errors的收集行为见 commitMutation.js——单条或多条批量响应中的payload.errors会被累积起来在complete时作为onCompleted的第二个参数传入。这正是「字段级错误走onCompleted、data: null致命错误走onError」两条路径的分水岭。端到端 fixture 全解从配置到断言本 fixture 是relay-e2e-test包中「Markdown 驱动」的端到端测试Markdown 里带title的代码块会被抽成真实文件steps块会被解析成交互步骤测试最终与同名的.snap.md快照比对。下面按原文档顺序逐一拆解。1. Relay 配置relay.config.json{ src: ./, schema: ./schema.graphql, language: typescript }src源码目录编译器从这里收集 GraphQL 标签schemaschema 文件路径本 fixture 中由 Grats 从 TS 源码生成见下文language生成产物的语言本 fixture 使用typescript以便useLazyLoadQueryAppTestQuery这样的泛型获得完整类型检查。2. Schema 定义server.ts基于 Grats/** gqlQueryField */ export function greeting(): string { return Ready; } /** gqlMutationField */ export function doSomething(args: { input: string }): string { return ok; }这是通过 Grats 的gqlQueryField/gqlMutationField注释从 TypeScript 函数直接生成 GraphQL schema 的写法greeting: String!成为 query 字段doSomething(input: String!): String!成为 mutation 字段。测试脚手架在 runFixture.js 中依次执行grats生成schema.graphql、再执行relay-compiler生成__generated__/*.graphql产物随后对整个 fixture 跑一次tsc --noEmit类型检查诊断会进入快照见.snap.md顶部的 Type Errors 段。3. 模拟data: null响应的自定义 NetworkApp.tsximport { Suspense, useState, useTransition } from react; import { RelayEnvironmentProvider, useLazyLoadQuery, useMutationAction_EXPERIMENTAL, } from react-relay; import { graphql, Environment, Network, Observable } from relay-runtime; import { gratsNetwork } from ../GratsNetwork; import { AppTestQuery } from ./__generated__/AppTestQuery.graphql; import { AppDoSomethingMutation } from ./__generated__/AppDoSomethingMutation.graphql; const dataNullNetwork Network.create((operation, variables) { if (operation.operationKind mutation) { return Observable.create((sink) { sink.next({ data: null, errors: [{ message: Non-nullable field failed }], }); sink.complete(); }); } return gratsNetwork.execute(operation, variables, {}, null); }); const testEnvironment new Environment({ network: dataNullNetwork });关键点按操作类型分流自定义网络只在operationKind mutation时伪造data: null响应queryAppTestQuery则回落给 GratsNetwork.ts 中的真实 graphql 执行器保证greeting正常渲染出 Ready。伪造方式Observable.create直接sink.next({data: null, errors: [{message: ...}]})后complete()精确模拟「HTTP 200、GraphQL 语义级失败」的服务端响应绕开真实网络。这也是文档所述「The environment uses a custom network that returns{data: null, errors: [...]}for mutations, simulating a non-nullable top-level field error」的实现。4. 组件层用startTransitiontry/catch捕获function Content() { const data useLazyLoadQueryAppTestQuery( graphql query AppTestQuery { greeting } , {}, ); const commitAction useMutationAction_EXPERIMENTALAppDoSomethingMutation( graphql mutation AppDoSomethingMutation($input: String!) { doSomething(input: $input) } , ); const [errorMessage, setErrorMessage] useStatestring | null(null); const [isPending, startTransition] useTransition(); return ( div div{data.greeting}/div button disabled{isPending} onClick{() { startTransition(async () { try { await commitAction({ input: test }); } catch (err) { setErrorMessage((err as Error).message); } }); }} Submit /button {errorMessage ! null divCaught error: {errorMessage}/div} /div ); }useLazyLoadQuery先渲染greeting页面初始显示 ReadycommitAction以AppDoSomethingMutation带$input: String!变量为参数点击 Submit 后startTransition包裹的异步函数执行await commitAction({input: test})由于网络返回data: nullPromise 被reject进入catch把错误消息写入errorMessage状态UI 渲染出 Caught error: ...isPending用于在 transition 进行中禁用按钮避免重复提交。注意commitAction的返回类型是PromiseTData因此在成功路径下还能拿到TData响应与乐观更新useOptimistic配合实现 action 模式的乐观 UI详见同目录 useMutationAction-optimistic.md。5. 交互断言Steps 块wait Ready click button Submit wait Caught error:这段 DSL 由 runInteractions.js 执行wait对应findByText/findByRole等待元素出现click对应userEvent.click。它验证了完整时序——先确认 query 数据 Ready 渲染点击 Submit再确认错误消息 Caught error: 出现。解析规则click Name、click role Name、type、wait等都在该文件的USAGE注释与parseStep中有完整定义。6. 快照验证.snap.mduseMutationAction-top-level-field-error.snap.md 记录了两次验证结果Type Errors 段App.tsx(17,9): error TS2353: ... errors does not exist in type GraphQLResponseWithExtensionsOnly | ...——这证明 fixture 的sink.next伪造对象在严格类型下并不完全符合relay-runtime的响应类型定义GraphQLSingularResponse的data类型是GraphQLResponseWithExtensionsOnly | readonly GraphQLSingularResponse[]没有errors字段直接共存于data: null分支。这是一个刻意保留、供人审视的类型瑕疵快照机制使它在 CI 中持续可见而不是悄悄被修掉或掩盖。HTML 段最终渲染的 DOM 中出现了Caught error: No data returned for operation ... got error(s): Non-nullable field failed证明整条错误链路端到端打通。与其他错误场景的对比同一个mutationsfixture 目录还覆盖了data: null之外的若干错误形态可以对照理解本场景的定位场景响应形态行为路径对应 fixture顶级字段错误本文data: nullerrors致命错误 →onError→ Promise rejectuseMutationAction-top-level-field-error.md字段级错误data存在 errors非致命正常完成错误进onCompleted第二参数useMutationAction-field-errors.md、useMutationAction-catch-field-error.md网络错误传输层异常 /Observable发出error致命错误 →onError→ Promise rejectuseMutationAction-network-error-catch.md、useMutationAction-network-error-boundary.md此外还有 useMutationAction-form-action.md把commitAction直接作为表单action属性使用与 useMutationAction-sequential.md连续提交等 fixture共同构成useMutationAction_EXPERIMENTAL的行为矩阵。如何在真实应用中复刻这条测试路径本 fixture 是可运行的测试不只是文档。测试由 fixtures-test.js 驱动读取目录下所有非.snap.md的 Markdown → parseMarkdown.js 用正则抽取title代码块与steps块 → setupTempDir.js 在临时目录重建文件并生成指向本仓库relay-runtime/react-relay的 tsconfigpaths使类型检查针对当前 commit 的.d.ts→ runFixture.js 依次跑 Grats、relay-compiler、tsc → React Testing Library 渲染并执行交互 → 与快照比对。在packages/relay-e2e-test下执行yarn test:e2e或jest --config jest.config.js即可运行全部 fixture。应用到你的项目中时只需保留两个核心模式伪造网络层Network.create中对 mutation 返回{data: null, errors: [...]}即可零成本复现「非空顶级字段失败」组件层兜底始终用try/catch包裹await commitAction(...)或在更外层放置错误边界捕获未处理的 rejection因为data: null永远不会走onCompleted的成功分支。【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relay创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

图解Picasso位图解码内幕:inSampleSize采样、inBitmap复用与OutOfMemoryError防御机制

图解Picasso位图解码内幕:inSampleSize采样、inBitmap复用与OutOfMemoryError防御机制

图解Picasso位图解码内幕:inSampleSize采样、inBitmap复用与OutOfMemoryError防御机制 【免费下载链接】picasso A powerful image downloading and caching library for Android 项目地址: https://gitcode.com/gh_mirrors/pic/picasso Picasso 是 Android …

2026/9/24 3:22:58 阅读更多 →
PouchDB 7.0 发布解读:移除 WebSQL 后的 17.5% 体积瘦身与确定性修订机制

PouchDB 7.0 发布解读:移除 WebSQL 后的 17.5% 体积瘦身与确定性修订机制

PouchDB 7.0 发布解读:移除 WebSQL 后的 17.5% 体积瘦身与确定性修订机制 【免费下载链接】pouchdb :kangaroo: - PouchDB is a pocket-sized database. 项目地址: https://gitcode.com/gh_mirrors/po/pouchdb 本篇技术指南围绕 PouchDB 7.0.0 版本发布内容展…

2026/9/24 2:40:50 阅读更多 →
RxJS v4 retry 操作符深度解析:重试语义、源码原理与实战用法

RxJS v4 retry 操作符深度解析:重试语义、源码原理与实战用法

RxJS v4 retry 操作符深度解析:重试语义、源码原理与实战用法 【免费下载链接】RxJS The Reactive Extensions for JavaScript 项目地址: https://gitcode.com/gh_mirrors/rxj/RxJS 本指南围绕 RxJS v4 中 Rx.Observable.prototype.retry([retryCount]) 操作…

2026/9/23 12:22:02 阅读更多 →

最新新闻

AntConc语料库分析入门:词频统计与KWIC检索实战指南

AntConc语料库分析入门:词频统计与KWIC检索实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 9:34:59 阅读更多 →
芯片烧录程序版本管理:从命名规范到MES防错与追溯

芯片烧录程序版本管理:从命名规范到MES防错与追溯

芯片烧录这个环节,看起来只是产线上一道不起眼的工序,但它往往是整个生产流程里最容易"埋雷"的地方。我做嵌入式生产和工艺支持这些年,见过太多因为烧录程序版本混乱导致的批量事故:产线烧错固件、返修机烧回旧版本、客…

2026/9/26 9:34:59 阅读更多 →
韩国商标注册怎么办理?

韩国商标注册怎么办理?

1. 韩国商标注册有什么用? 韩国是亚洲重要的消费市场与品牌高地,企业进入韩国市场前,先行完成商标注册能够有效防止品牌在韩国境内被抢注或仿冒。根据韩国特许厅(KIPO)的现行制度,商标专用权自注册公告之日…

2026/9/26 9:34:59 阅读更多 →
Corundum移植到Bittware VV4:100G NIC系统级适配实战

Corundum移植到Bittware VV4:100G NIC系统级适配实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 9:34:59 阅读更多 →
票房预测的机器学习落地:特征工程、模型选型与避坑指南

票房预测的机器学习落地:特征工程、模型选型与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 9:34:59 阅读更多 →
OpenClaw 适合普通人使用吗?先配好 TaoToken 再判断

OpenClaw 适合普通人使用吗?先配好 TaoToken 再判断

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 9:33:59 阅读更多 →

日新闻

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、…

2026/9/26 0:00:25 阅读更多 →
学校官网模拟全流程实践:从页面布局到后端接口与部署

学校官网模拟全流程实践:从页面布局到后端接口与部署

如果你正在找一门 Web 大作业的题目,或者刚开始接触 Web 前端开发想做点能拿来展示的东西,“学校官网模拟”几乎是最稳的选择。题目看着简单,但要把导航、新闻列表、轮播 Banner、二级页面、后台数据都串起来,其实已经把前端布局、…

2026/9/26 0:00:25 阅读更多 →
超级玛丽游戏源码C++:从零搭建横版跳跃游戏工程

超级玛丽游戏源码C++:从零搭建横版跳跃游戏工程

简介:这是一份面向游戏开发初学者与C进阶学习者的超级玛丽(超级马里奥)游戏源码,基于C面向对象编程实现,适合想通过经典项目理解游戏主循环、角色类设计、地图关卡加载与物理碰撞检测的读者参考。压缩包共49个文件&…

2026/9/26 0:00:25 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/25 19:27:14 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/25 11:15:26 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/25 20:29:09 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/25 20:29:43 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/25 20:29:31 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/25 19:27:26 阅读更多 →