如何处理联合类型和交叉类型?

联合类型和交叉类型,是TypeScript类型系统里两个非常基础但又有点“反直觉”的概念。它们名字听起来像是对立的,但其实解决的是完全不同的问题。用好了,你的类型表达能力能上一个大台阶。

先说联合类型,符号是竖线|。它的意思很简单:“这个值可以是A类型,或者B类型”。它描述的是多种可能性之一。

type ID = number | string;
let userId: ID = 100; // 可以
userId = 'abc123'; // 也可以

函数参数也经常这么用:

function formatInput(input: string | number) {
  // ...
}

你看,input可能是字符串,也可能是数字。但这里就引出了处理联合类型时最关键的技巧:类型收窄。在函数体里,你不能直接调用input.toUpperCase(),因为数字没这个方法。你必须先判断当前到底是哪种可能。

function formatInput(input: string | number) {
  if (typeof input === 'string') {
    return input.toUpperCase(); // 这里input被收窄为string
  } else {
    return input.toFixed(2); // 这里input被收窄为number
  }
}

typeofinstanceof,或者检查特定的属性(“可辨识联合”),都能帮编译器确定当前的具体类型。这是处理联合类型的标准姿势。

交叉类型就完全是另一种思路了,符号是&。它的意思是:“这个值必须同时满足A类型和B类型的所有要求”。它描述的是类型的合并,创造出一个拥有所有特性的新类型。

interface Named {
  name: string;
}
interface Aged {
  age: number;
}
type Person = Named & Aged;

const p: Person = {
  name: '张三',
  age: 30 // 少一个都会报错
};

这有点像混入(mixin)。但交叉类型真正的威力(和坑)在于处理对象类型。它并不是简单地把两个对象拼起来,而是进行数学意义上的“交集”。对于属性名相同但类型不同的情况,结果类型会是never,因为没有任何值能同时满足两个冲突的类型。

那什么时候用哪个呢?我的经验法则挺简单:当你需要表达“或”的关系,比如一个函数能接受多种形态的参数,就用联合类型。当你需要表达“且”的关系,比如组合多个接口或特性来创建一个更丰富的类型,就用交叉类型。

实战中,它们经常一起出现。比如一个常见的模式:定义一个基础配置,再定义一个带默认值的配置,最后交叉起来。

interface BaseConfig {
  url: string;
}
interface DefaultConfig {
  timeout: number;
}
type FullConfig = BaseConfig & Partial<DefaultConfig>;

这里FullConfig必须有url,而timeout是可选的(因为被Partial包装了)。这就是交叉类型和工具类型的组合技。

理解它们的关键,是别再纠结于“联合”和“交叉”的字面意思。把它们看作描述类型集合的运算符:联合类型是取并集(值属于集合A或集合B),交叉类型是取交集(值必须同时属于集合A和集合B)。带着这个集合论的视角去看代码,很多奇怪的现象就说得通了。

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享