TypeScript中如何严格校验可选属性,避免非法类型赋值?
解决TypeScript联合类型接受额外属性的问题
问题根源
TypeScript采用结构类型系统——只要对象满足目标类型的所有必填属性,就算携带额外属性也会被判定为类型兼容。直接赋值字面量时会触发额外属性检查,但如果变量先被标注为更宽泛的类型(比如示例中的c)再赋值,该检查就不会生效,最终导致bar能被赋值给a | b类型的foo,但实际包含服务器不接受的c属性。
可行解决方案
1. 给联合类型成员添加禁止额外属性的约束
修改a和b类型,通过never类型明确禁止c属性存在:
type a = {a : number, b: number, c?: never}; // 不允许存在c属性 type b = {a: number, b: number, id: number, c?: never}; // 不允许存在c属性 type c = {a: number, b: number, c?: number}; const bar: c = {a: 12, b: 10, c: 15}; const foo: a | b = bar; // 现在会触发编译错误
never类型表示该属性不能有任何有效值,只要对象携带c属性(哪怕是可选状态),就无法通过类型校验。
2. 使用类型守卫做严格校验
编写类型守卫函数,既检查对象是否符合a | b的结构要求,又过滤掉携带c属性的对象:
type a = {a : number, b: number}; type b = {a: number, b: number, id: number}; type c = {a: number, b: number, c?: number}; function isAOrB(obj: unknown): obj is a | b { if (typeof obj !== 'object' || obj === null) return false; const target = obj as Record<string, unknown>; // 先校验必填属性 if (typeof target.a !== 'number' || typeof target.b !== 'number') return false; // 禁止存在c属性 if (target.c !== undefined) return false; // 校验b类型的可选id属性 if (target.id !== undefined) return typeof target.id === 'number'; return true; } const bar: c = {a: 12, b: 10, c: 15}; if (isAOrB(bar)) { const foo: a | b = bar; // 仅通过校验的对象能进入此分支 } else { // 自动剔除c属性,生成符合要求的对象 const { c, ...cleanedBar } = bar; const foo: a | b = cleanedBar; }
3. 手动清理对象多余属性
在发送给服务器前,直接剔除多余的c属性,确保对象结构完全符合要求:
type a = {a : number, b: number}; type b = {a: number, b: number, id: number}; type c = {a: number, b: number, c?: number}; const bar: c = {a: 12, b: 10, c: 15}; // 通过解构剔除c属性,得到结构干净的对象 const { c, ...cleanedBar } = bar; const foo: a | b = cleanedBar; // 发送请求时使用清理后的对象 fetch('/api/your-endpoint', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(cleanedBar) });
4. 用类型断言配合Omit移除多余属性
如果需要强制转换类型,可以结合Omit类型先剔除c属性,再做类型断言:
type a = {a : number, b: number}; type b = {a: number, b: number, id: number}; type c = {a: number, b: number, c?: number}; const bar: c = {a: 12, b: 10, c: 15}; // 移除c属性后断言为a | b类型 const foo: a | b = bar as Omit<c, 'c'>;
补充说明
此问题属于TypeScript结构类型系统的设计特性,目前官方相关讨论尚未有最终解决方案。
内容的提问来源于stack exchange,提问作者Amogh Rijal
相关产品推荐
相关产品推荐

