You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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不会认为访问该属性是非法操作。

正确实现方案

核心思路是将赋值校验和使用时的类型定义分离:

  1. 保留干净的基础接口,不声明多余的互斥属性,保证类型收窄后无法访问不存在的属性
  2. 单独定义带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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 21:12:41