React useState对交叉判别联合类型的类型检查不严格问题
问题:React useState中交叉联合类型的类型检查不严格问题
类型定义
我们定义了一个由两个判别联合类型交叉得到的Errors类型:
type Errors = ({ err1: false; } | { err1: true; msg1: string; }) & ({ err2: false; } | { err2: true; msg2: string; })
纯TypeScript环境下的正常表现
在直接声明变量并赋值时,TypeScript会严格检查多余属性,添加不属于Errors的属性会触发IDE错误:
const customError: Errors = { err1: false, err2: false, attributeThatShouldntBeAllowed: 1 // 此处会触发IDE错误 }
React useState中的异常表现
当把Errors作为useState的泛型参数使用时,初始化是正常的:
const [errors, setErrors] = useState<Errors>({ err1: false, err2: false }) // 初始化符合类型要求,无问题
但调用setErrors时,通过回调返回带有多余属性的对象,却不会触发任何类型错误:
setErrors(prevState => ({ ...prevState, attributeThatShouldntBeAllowed: "lol", anotherAttribute: 1 })) // 此处未触发类型错误
相关tsconfig配置
测试沙箱的tsconfig.json
{ "compilerOptions": { "target": "es2016", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true } }
项目的tsconfig.json
{ "compilerOptions": { "target": "es5", "module": "commonjs", "strict": true, "jsx": "react", "allowSyntheticDefaultImports": true, "baseUrl": ".", "paths": { "@domain/*": ["./src/domain/*"], "@infra/*": ["./src/infra/*"], "@types/*": ["./src/types/*"], "@controllers/*": ["./src/controllers/*"] } }, "include": ["src", "node_modules"], }
原因分析
这个现象的核心是TypeScript的多余属性检查规则的适用场景限制:
- 直接赋值场景触发严格检查:当你把对象字面量直接赋值给一个显式声明了类型的变量时(比如
const customError: Errors = {...}),TypeScript会启动「多余属性检查」,严格禁止添加类型定义中不存在的属性,这是为了帮开发者排查拼写错误或冗余属性。 - 函数返回值仅做结构兼容性检查:而
setErrors的回调函数返回的对象,属于函数返回值场景,TypeScript只会做结构类型兼容性验证——只要返回的对象包含Errors类型要求的所有必填属性,且属性类型匹配,就认为该对象可以赋值给Errors类型,不会额外检查多余属性。 - 交叉联合类型的兼容性特性:你的
Errors类型是两个联合类型的交叉,展开后所有可能的成员都只要求err1/err2相关属性,带有多余属性的对象完全满足这些成员的结构要求,因此在结构兼容性检查中会被判定为合法。
简单来说,「多余属性检查」是TypeScript给直接赋值场景加的额外严格校验,而函数返回值走的是常规的结构类型兼容逻辑,不会触发这个额外校验。
内容的提问来源于stack exchange,提问作者Pedro Faria Fernandes
相关产品推荐
相关产品推荐

