TypeScript如何实现size与fixSize属性互斥仅可设置其一
问题
需求:实现size和fixSize两个属性互斥,同一时间仅允许设置其中一个。
最初的实现无法拦截同时赋值的情况:
interface Size { size: number; } interface FixSize { fixSize: number; } type Demo = Size | FixSize; // 不会抛出类型错误,不符合预期 let a: Demo = { size: 1, fixSize: 1 }
现有方案的缺陷
使用可选never标记互斥属性的方案,可以拦截同时赋值的情况,但类型收窄后依然可以访问互斥属性,不符合预期:
interface Size { size: number; fixSize?: never; } interface FixSize { fixSize: number; size?: never; } type Demo = Size | FixSize; let a: Demo = { // 这里可以正常拦截,抛出类型错误 size: 1, fixSize: 1 } function sizeFn(a: Demo) { if (isSize(a)) { a.fixSize; // 不会报错,不符合预期:不允许访问fixSize属性 a.size; } } function isSize(a: Demo): a is Size { return 'size' in a; }
缺陷原因:互斥属性被显式声明为接口的可选成员,属于类型的一部分,因此收窄后TS不会认为访问该属性是非法操作。
正确实现方案
核心思路是将赋值校验和使用时的类型定义分离:
- 保留干净的基础接口,不声明多余的互斥属性,保证类型收窄后无法访问不存在的属性
- 单独定义带
never标记的校验类型,仅用于赋值/传参时的合法性校验,不参与后续业务逻辑的类型推导
具体实现
1. 基础类型定义
// 干净的业务类型,不包含互斥属性 interface Size { size: number; } interface FixSize { fixSize: number; } // 仅用于赋值校验的内部类型,不直接在业务逻辑中使用 type ExclusiveDemo = | (Size & { fixSize?: never }) | (FixSize & { size?: never });
2. 变量赋值场景(TS 4.9+ 推荐)
用satisfies操作符做合法性校验,同时保留值的原始推导类型为Size | FixSize:
// 同时传两个属性会直接报错 const badDemo = { size: 1, fixSize: 1 } satisfies ExclusiveDemo; // 合法赋值,类型自动推导为 Size const sizeDemo = { size: 1 } satisfies ExclusiveDemo; // 合法赋值,类型自动推导为 FixSize const fixSizeDemo = { fixSize: 1 } satisfies ExclusiveDemo;
3. 函数传参场景
用泛型约束做参数校验,参数类型会被推导为干净的Size | FixSize:
function handleDemo<T extends ExclusiveDemo>(demo: T) { // 直接用原生in操作符收窄类型,不需要自定义类型守卫 if ('size' in demo) { // demo.fixSize; // 直接报错:类型“Size”上不存在属性“fixSize” console.log('size值:', demo.size); // 正常访问 } else { // demo.size; // 直接报错:类型“FixSize”上不存在属性“size” console.log('fixSize值:', demo.fixSize); // 正常访问 } } // 传参自动校验 handleDemo({ size: 1 }); // 正常 handleDemo({ fixSize: 1 }); // 正常 handleDemo({ size: 1, fixSize: 1 }); // 报错,不允许同时传两个属性
4. 低版本TS兼容方案(<4.9,无satisfies语法)
用辅助创建函数做赋值校验:
function createDemo<T extends ExclusiveDemo>(demo: T): T { return demo; } const sizeDemo = createDemo({ size: 1 }); // 类型为Size,访问fixSize直接报错 const badDemo = createDemo({ size:1, fixSize:1 }); // 直接抛出类型错误
实现效果
- 赋值/传参时,同时传入
size和fixSize会直接抛出类型错误 - 类型收窄后,访问未设置的互斥属性会直接报“属性不存在”的类型错误,完全符合互斥属性的访问限制
- 不需要额外编写自定义类型守卫,直接用原生
in操作符即可完成类型收窄
内容的提问来源于stack exchange,提问作者zongchen
相关产品推荐
相关产品推荐

