ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

在 Convex 后端项目中使用 TypeScript exactOptionalPropertyTypes 的完整实践指南

2026/9/23 12:32:45 拓冰建站 浏览量
在 Convex 后端项目中使用 TypeScript exactOptionalPropertyTypes 的完整实践指南 在 Convex 后端项目中使用 TypeScript exactOptionalPropertyTypes 的完整实践指南【免费下载链接】convex-backendThe open-source reactive database for app developers项目地址: https://gitcode.com/gh_mirrors/co/convex-backend导读exactOptionalPropertyTypes是 TypeScript 5.0 起引入的严格类型检查选项它改变了可选属性prop?: T的类型语义读取时属性类型不再隐式包含undefined写入时必须显式区分属性缺失与属性值为undefined。本指南以 convex-backend 仓库中的typescript-exact-optional-property-types示例项目为骨架结合其tsconfig.json、Convex 函数源码与自动生成的类型文件讲解如何在 Convex 应用中开启该选项、它对v.optional()校验器生成的文档类型有何影响以及如何借助noUncheckedIndexedAccess、skipLibCheck等配套配置获得更严格的类型安全。一、示例项目定位验证严格类型模式下的 Convex 开发体验仓库中的 npm-packages/private-demos/typescript-exact-optional-property-types/README.md 对该示例的定位非常精炼This is a recent TypeScript version with the tsconfig.json optionexactOptionalPropertyTypes: trueset.——即使用较新版本的 TypeScript项目声明依赖typescript: ^5.9.2见 package.json并在 tsconfig 中开启exactOptionalPropertyTypes的演示项目。它属于仓库中 private-demos 目录下的一组类型能力验证型示例同目录还包含typescript-modern、typescript-old、typescript-exact-optional-property-types等分别用于验证不同 TS 版本与编译选项下的 Convex 兼容性。这类示例的价值在于用最小可运行的项目验证某个激进 TypeScript 配置是否与 Convex 的 schema 校验器、自动生成类型convex/_generated以及convex dev工作流兼容。项目结构如下npm-packages/private-demos/typescript-exact-optional-property-types/ ├── convex/ │ ├── _generated/ # npx convex dev 自动生成的类型api.d.ts、dataModel.d.ts 等 │ ├── messages.ts # 演示 exactOptionalPropertyTypes 行为的 query 函数 │ ├── schema.ts # 定义了含可选字段的 messages 表 │ └── tsconfig.json # Convex 函数目录自身的 TS 配置 ├── package.json # scripts: dev convex devbuild tsc ├── tsconfig.json # 根配置开启 exactOptionalPropertyTypes 等严格选项 └── turbo.json # turbo build 任务仅类型检查、无产物输出其中turbo.json明确声明build任务的outputs: []并在注释中说明The build script only typechecks; it emits no files——也就是说这个示例的build脚本即tsc只用于类型检查不产出编译文件根 tsconfig 中亦配置了noEmit: true。二、根 tsconfig.json 逐项解读如何组合出极致严格的配置示例项目的根 tsconfig.json 并非随手打开几个开关而是一套经过推敲的严格配置组合其注释甚至给出了出处约等于 microsoft/TypeScript PR #61813 所讨论的推荐配置。逐项拆解如下2.1 环境设置module: nodenext, target: esnext, lib: [esnext], types: [node], sourceMap: true, declaration: false, declarationMap: falsemodule: nodenext配合target: esnext适用于 Node.js 端的现代 ESM 工程lib: [esnext]只引入 ESNext 标准库types: [node]引入 Node 类型需npm install -D types/node示例 devDependencies 中已包含types/node: ^18.17.0。sourceMap: true保留调试能力。declaration/declarationMap显式关闭配置注释说明了原因This doesnt work with the inferred types of convex functions——Convex 函数query/mutation的返回类型依赖运行时推断declaration与其不兼容。这是一个从源码配置中可以确认的重要事实在开启严格选项的 Convex 工程中不要试图开启.d.ts产物生成。2.2 严格类型检查选项本示例的核心noUncheckedIndexedAccess: true, exactOptionalPropertyTypes: true, strict: truestrict: true是 TypeScript 推荐的基础严格模式。noUncheckedIndexedAccess: true对数组/对象的索引访问如stuff[0]将额外叠加undefined强制开发者处理越界与缺失情况。exactOptionalPropertyTypes: true本示例的主角。开启后声明为prop?: string的属性其读取类型不再包含undefined同时给可选属性赋undefined会报错除非该属性类型本就声明为string | undefined。2.3 推荐选项与工程配套jsx: react-jsx, verbatimModuleSyntax: true, isolatedModules: true, noUncheckedSideEffectImports: true, moduleDetection: force, skipLibCheck: true, noEmit: true, forceConsistentCasingInFileNames: trueverbatimModuleSyntaxisolatedModules保证按源码原样保留导入语法、且每个文件可独立编译是跨 bundler / NodeNext 工程的一致选择。noUncheckedSideEffectImports对仅用于副作用导入的文件做存在性检查TS 5.6 选项。skipLibCheck: true值得特别说明配置注释明确指出the convex package doesnt typecheck when using exactOptionalPropertyTypes——即convex npm 包自身的类型声明.d.ts在exactOptionalPropertyTypes下无法通过类型检查因此必须开启skipLibCheck跳过对依赖库声明文件的检查。这是从示例配置中直接可证的关键经验第三方库类型尚未适配该严格选项时skipLibCheck是必要的妥协手段。三、schema 侧的准备用 v.optional() 定义可选字段Convex 的 schema 通过校验器validators描述数据结构。示例的 convex/schema.ts 定义了messages表import { defineSchema, defineTable } from convex/server; import { v } from convex/values; export default defineSchema({ messages: defineTable({ author: v.string(), body: v.string(), optionalString: v.optional(v.string()), objectWithOptionalString: v.object({ optionalString: v.optional(v.string()), }), }), });这里展示了两种可选字段形态表级可选字段optionalString: v.optional(v.string())——该字段在文档中可以缺失值为string或缺失。嵌套对象中的可选字段objectWithOptionalString是一个v.object其内部同样含有v.optional(v.string())字段用于验证exactOptionalPropertyTypes对嵌套校验器类型推断的影响。值得强调的是v.optional(...)生成的可选字段与 TypeScript 的?:可选属性在语义上天然呼应schema 的字段可缺失对应 TS 的属性可缺失。这正是exactOptionalPropertyTypes能在此类项目中产生连锁影响的原因——Convex 会根据 schema 生成Doc类型见下文可选字段会被映射为可缺省属性。四、函数代码实测exactOptionalPropertyTypes 在文档访问中的行为差异示例的核心演示代码在 convex/messages.ts一个名为list的 query 函数。它以ctx.db.query(messages).collect()取出全部文档随后围绕可选字段做了细致的类型验证代码中的ts-expect-error注释本身就是行为断言值得逐段分析4.1 配合 noUncheckedIndexedAccess 的数组访问const stuff await ctx.db.query(messages).collect(); // (noUncheckedIndexedAccess) const doc stuff[0]!;在noUncheckedIndexedAccess下stuff[0]的类型是Docmessages | undefined因此示例用非空断言!显式声明这里一定有元素。注释直接标注了该行为由noUncheckedIndexedAccess引起。4.2 可选字段的读取类型中不再隐式携带 undefined// exactOptionalPropertyTypes isnt any different when you access this const optionalField: undefined | string doc.optionalString;代码注释明确说明exactOptionalPropertyTypes isnt any different when you access this——读取可选属性时其类型与未开启该选项时没有区别仍是string | undefined因为 Convex 生成的可选字段类型本就如此。这一结论很重要exactOptionalPropertyTypes的差异主要体现在赋值与可选属性与undefined的区分而非读取端。4.3 用解构 in 操作符区分缺失与存在示例通过解构把文档拆成三部分const { _id, _creationTime, body: _body, author: _author, objectWithOptionalString, ...justOptional } doc; if (optionalString in justOptional) { const exists: string justOptional.optionalString; console.log(exists); } else { const dne: undefined justOptional.optionalString; // ts-expect-error undefined is not assignable to string const exists: string justOptional.optionalString; console.log(dne, exists); }通过 rest 解构得到的justOptional只含optionalString一个可选字段。用in操作符做存在性收窄命中if分支时justOptional.optionalString收窄为string进入else分支时它被收窄为undefined即属性不存在此时再把它赋给string就会触发ts-expect-error断言。这一模式展示了在严格选项下安全访问可选属性的推荐写法。4.4 嵌套可选字段的已知限制demo 的核心结论if (optionalString in objectWithOptionalString) { // ts-expect-error building convex with exact-optional-property-types fixes this const exists: string justOptional.optionalString; console.log(exists); } else { // ts-expect-error building convex with exact-optional-property-types fixes this const dne: undefined justOptional.optionalString; // ts-expect-error undefined is not assignable to string const exists: string justOptional.optionalString; console.log(dne, exists); }注意这段代码的三个ts-expect-error断言其注释揭示了一个已知缺陷当前 Convex 根据 schema 生成文档类型时嵌套对象objectWithOptionalString内的可选字段在exactOptionalPropertyTypes下没有被完整建模——justOptional在else分支中本应收窄为undefined但断言注释写道building convex with exact-optional-property-types fixes this即期望未来 Convex 构建链适配该选项后消除此类误报。从源码结构看这是示例作者有意留下的待改进标记它验证了「Convex 生成类型在严格可选属性语义下的边界」。五、Convex 自动生成的类型exactOptionalPropertyTypes 的落点运行npx convex dev后CLI 会在 convex/_generated 目录生成类型文件。其中 dataModel.d.ts 是理解上文行为的关键export type DocTableName extends TableNames DocumentByName DataModel, TableName ; export type DataModel DataModelFromSchemaDefinitiontypeof schema;Docmessages由schema.ts通过DataModelFromSchemaDefinition推导而来也就是说schema 中v.optional(v.string())定义的可选字段最终决定了doc.optionalString的类型形态string | undefined属性可缺省。这也解释了 4.2 节读取时类型不含差异的结论——文档类型中可选属性本来就是string | undefinedexactOptionalPropertyTypes的严格化主要体现在别处对象字面量赋值、函数参数等场景。六、convex/tsconfig.json函数运行时环境的独立配置与根配置并列的 convex/tsconfig.json 描述的是Convex 函数的运行环境用于对函数代码做类型检查。其注释明确了哪些是Convex 必需项、哪些可自由修改必需项不可改动target: ESNext、lib: [ES2023, dom]、forceConsistentCasingInFileNames、module: ESNext、isolatedModules、noEmit。可修改项allowJs、strict、moduleResolution: Bundler、jsx: react-jsx、skipLibCheck、allowSyntheticDefaultImports。包含范围include: [./**/*]但exclude: [./_generated]——自动生成代码不参与函数目录自身的类型检查。这套根配置 convex 子配置的双层结构是 Convex 工程的通用模式根配置管整个 monorepo 包这里是 strict exactOptionalPropertyTypes 的严格验证convex/子配置约束函数运行环境。示例刻意只在根配置中开启exactOptionalPropertyTypes从而把验证焦点集中在 demo 目的上。七、如何运行与验证在 package.json 中可以看到两个脚本scripts: { dev: convex dev, build: tsc }类型检查在示例目录执行npm run build等价于tsc它会按根 tsconfig 校验全部源码。若convex/messages.ts中的ts-expect-error断言被破坏例如 Convex 类型生成已适配exactOptionalPropertyTypes使某处不再报错tsc会因未使用的 expect-error 指令而失败——这正是该示例作为回归验证的机制一旦 Convex 修复了嵌套可选字段的建模问题构建就会提示移除相应断言。运行开发服务器执行npm run dev等价于convex dev可启动本地开发环境CLI 会同步生成convex/_generated下的类型文件并可在本地执行list查询观察行为。需要说明的适用前提该示例依赖convex包workspace 引用与 TypeScript 5.9.x由于exactOptionalPropertyTypes对convex包自身声明文件不友好见 2.3 节skipLibCheck: true是当前可运行的必需条件。八、实践要点小结主题结论证据位置可选字段读取exactOptionalPropertyTypes开启前后读取文档可选字段的类型无差异string \| undefinedconvex/messages.ts安全访问模式用in操作符 rest 解构收窄属性缺失 / 存在两种状态convex/messages.ts嵌套可选字段当前生成类型对嵌套v.object内的可选字段建模不完整存在ts-expect-error待修复标记convex/messages.ts必须 skipLibCheckconvex 包的类型声明在exactOptionalPropertyTypes下无法通过检查tsconfig.json不开启 declarationConvex 函数推断类型与declaration不兼容tsconfig.jsonschema 侧写法可选字段统一用v.optional(...)表级与嵌套对象皆可convex/schema.ts简而言之typescript-exact-optional-property-types是 convex-backend 仓库中一个以极小代价验证激进严格类型配置的样例它把exactOptionalPropertyTypes与noUncheckedIndexedAccess、strict组合成一套可复制的 tsconfig 模板用带断言的 query 函数把该选项在 Convex 文档类型上的行为差异固化下来并如实标注了当前存在的兼容性边界依赖库skipLibCheck、嵌套可选字段的生成类型缺陷。对于希望在 Convex 工程中推进类型严格的开发者这份配置与代码即为现成的起点与对照基准。【免费下载链接】convex-backendThe open-source reactive database for app developers项目地址: https://gitcode.com/gh_mirrors/co/convex-backend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考