Frontend Data Contracts(类型化网络边界)是一套可移植、与框架无关的规范,用于在 React 或 React Native 应用的网络边界处实现类型安全。它的目标很明确:数据从网络进入应用的那一刻,就不再是任意形状的 wire JSON,而是可信的、类型化的领域值;否则就变成一个单一的类型化错误。这个转换只发生在一个地方,任何未类型化的数据都不能逃逸出去。
【核心思想】 该规范包含五个核心概念:
- 一个客户端是唯一的 fetch 边界。单个类型化的 apiClient 包装 fetch,组件和 hooks 绝不直接调用 fetch/axios,边界可通过代码审查和 lint 强制执行。
- Parse, don't validate(解析而非校验)。wire JSON 在边界处被解析为领域类型,客户端返回后该值在下游处处可信,无需防御性 ?. 链,也无需在组件中重复检查形状。
- 一个信封。每个响应成功时为 { data },失败时为 { error }。客户端解包 data 并在出错时抛出,调用方直接拿到载荷或一个类型化异常。
- 一个归一化错误类型。服务端错误信封、非 2xx 状态、畸形响应体、网络失败和中断都变成单一的 ApiError,带有机器码、状态码和可选的逐字段错误。
- 标识符是 branded 的。领域 ID 是名义类型(InvoiceId、CustomerId),编译器会拒绝把一种 ID 用在需要另一种的地方,这是数据密集型 UI 中最常见的静默 bug。
【目录布局】 边界是 shared/ 下的一个文件夹:src/shared/api-client/ 包含 index.ts(barrel:apiClient、ApiError、类型)、client.ts(fetch 包装:buildUrl、headers、parse、动词)、config.ts(base URL 解析、默认 headers)、error.ts(ApiError 类 + code→message-key 映射)、types.ts(信封类型、HttpMethod、RequestOptions、字段错误)、client.test.ts(边界行为测试)。领域实体类型及其 schema 放在各自功能模块(modules/{feature}/types/)或共享契约包中,客户端对 T 泛型。
【使用方式】 每个动词返回调用方指定类型的解包数据载荷,任何失败都抛出 ApiError。组件永远看不到信封或原始响应。硬性规则包括:shared/api-client/ 之外不得有 fetch/axios/XMLHttpRequest,用 ESLint 规则强制;客户端不依赖框架,不含 toast、router、React,副作用放在查询层的 onError;通过 RequestOptions 传递 AbortSignal 以便查询层取消。
解析步骤在边界运行 schema parse,之后值即被信任。校验库可自选 Zod、Valibot、ArkType、io-ts,规则恒定:一个解析步骤把未知 wire 数据转换为类型化领域值。响应信封在客户端解包一次,解析步骤处理所有形状:204 No Content → undefined;{ error } → 抛出;非 2xx 且无良好信封 → 合成错误;{ data } → 返回 data。
Branded 标识符在解析边界处打标,运行时成本为零,brands 在编译时擦除。归一化错误类型把每种失败模式折叠为单一 ApiError,携带机器码、HTTP 状态、可选逐字段错误和用于本地化消息的稳定 key(它本身不做本地化,那是 UI 的职责)。副作用放在查询层:客户端抛出,查询层决定用户看到什么。服务端校验错误(fields)直接映射到表单字段错误。
【库适配】 规范恒定,数据获取库只改变 onError/解析挂载的位置:TanStack Query 在 queryFn/mutationFn 调用 apiClient,onError 按查询/变更或全局 QueryCache/MutationCache 处理;RTK Query 用 baseQuery 包装 apiClient,transformErrorResponse → ApiError;SWR 用 fetcher = (key) => apiClient.get(key),onError 在 SWRConfig 或按 hook;React Native 的普通 fetch hooks 用 useAsync 包装调用 apiClient,try/catch 设置类型化错误状态。RN 中客户端不变,fetch 和 AbortController 都存在,仅 credentials: "include"(cookie 认证)可能需要按认证方式换成 token header。
【适用场景】 需要统一网络层、减少防御性判空和深层 undefined 问题的 React/React Native 项目;希望把契约漂移在边界处显式失败而非泄漏到下游的场景;需要把服务端字段错误映射到表单、把错误消息通过 i18n 本地化的应用。
【限制】 仅在任务明确匹配其上游来源和本地项目上下文时使用。应用变更前需验证命令、生成代码、依赖、凭据和外部服务行为。示例不能替代环境特定测试、安全审查,以及对破坏性或高成本操作的用户批准。文中标注“essence”的片段是省略了项目依赖和方法的架构草图,不是完整客户端;泛型 get<Invoice> 类型参数只是编译时断言,不能建立运行时有效性,必须要求实际的 schema parse 才能把值当作 Invoice;解析失败要归一化为项目错误类型;授权独立于 schema 校验和 branded ID;适配器行为要绑定已安装库版本,不要假设存在不受支持的 per-query 回调。