TypeScript联合类型中undefined类型未被正确识别问题排查
首先若该问题已有相关提问我在此致歉,我未检索到对应内容,遇到的问题如下:
我定义了名为 BaseCondition 的接口,其中包含属性 isGeneralized,类型声明如下:
isGeneralized: boolean | undefined
但最终该属性的类型被识别为:
(property) BaseCondition.isGeneralized: boolean
我尝试测试其他类型组合例如 string | undefined,仍然会被仅识别为string等联合类型中除undefined外的基础类型。
该异常仅出现在API项目中,Web应用内相同写法可正常工作。我已将Web应用的tsconfig配置完整复制到API项目,但问题仍然存在。
当前使用的tsconfig.json配置如下:
{ "compilerOptions": { "module": "commonjs", "target": "es6", "lib": ["es6", "dom"], "outDir": "dist", "sourceMap": true, "moduleResolution": "node", "experimentalDecorators": true, "resolveJsonModule": true, "typeRoots": ["node_modules/@types"], "noUnusedLocals": true /* Report errors on unused locals. */, "noUnusedParameters": true /* Report errors on unused parameters. */, "allowSyntheticDefaultImports": true, // no errors on commonjs default import "allowJs": true, // include js files "checkJs": true, // typecheck js files "declaration": false, // don't emit declarations "emitDecoratorMetadata": true, "forceConsistentCasingInFileNames": true, "importHelpers": true, // importing helper functions from tslib "noEmitHelpers": true, // disable emitting inline helper functions "esModuleInterop": true, "noEmitOnError": true, "noFallthroughCasesInSwitch": true, "noImplicitAny": true, "noImplicitReturns": true, "noImplicitThis": true, "strictNullChecks": true, "pretty": true, "removeComments": true, "downlevelIteration": true, "inlineSources": true, "strictPropertyInitialization": false }, "include": ["src/**/*", "src/**/*.json"], "exclude": ["node_modules", "coverage", "**/*.test.ts"] }
请问如何配置才能让TypeScript编译器正确识别联合类型中声明的undefined类型?
这个问题的核心诱因是你开启了 emitDecoratorMetadata 配置,且API项目中对应用到 BaseCondition 类型的类属性使用了装饰器(后端API项目常用的class-validator、TypeORM、NestJS路由/DTO装饰器都会触发这个逻辑,Web项目通常不会在同类结构上大量使用属性装饰器,所以不会复现)。
TypeScript生成装饰器元数据时,本身不支持序列化联合类型,对于 T | undefined 这类包含undefined的联合类型,会默认丢弃undefined类型,仅输出基础类型T的元数据,这是TS装饰器元数据功能的已知设计限制,不是tsconfig配置写错导致的。
按以下优先级排查修复即可:
- 如果你不需要给对应属性生成运行时类型元数据,直接关闭
emitDecoratorMetadata即可恢复类型识别,这是最直接的方案 - 如果必须保留装饰器元数据(比如需要用class-validator做参数校验、TypeORM做表字段映射、NestJS依赖注入),不要依赖TS自带的元数据识别undefined,直接把属性改成可选属性写法即可:
这种写法下TS会正确识别该属性的类型为interface BaseCondition { isGeneralized?: boolean }boolean | undefined,同时不会影响装饰器的运行时逻辑 - 检查API项目是否全局引入了类型覆盖的声明文件:很多后端工具包会自带全局类型补丁,把
T | undefined的属性做收敛,检查src目录下是否有额外的.d.ts文件做了接口合并、类型映射的逻辑,把BaseCondition的属性类型重写了,可以通过在接口定义处按F12跳转到类型定义的位置,确认是否被其他声明覆盖 - 确认TS版本一致性:如果Web和API项目的TypeScript版本差了大版本(比如Web用4.7+,API用4.0以下),部分旧版本TS在开启
strictNullChecks时确实存在联合类型undefined被擦除的bug,把API项目的TS版本升到和Web项目完全一致即可
注意:不要为了修复这个问题关闭
strictNullChecks,会导致整体类型检查的严格度下降,带来更多空值相关的运行时bug。
内容的提问来源于stack exchange,提问作者Kreshel

