入个TypeScript的门(4)——联合、交叉类型

59 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

有时候我们在定义一个变量的时候,不太确定这个值最终会是什么类型,有可能是null,也有可能是对象。或者说我们声明一个函数,这个参数有可能是数字类型也有可能是字符串类型,那么我们该如何定义呢?

联合类型

我们在很多编程中都有一个运算符叫逻辑运算符,他包括&|!,而在TS中也有|这个运算符,不过它不叫或,而是叫联合类型。不过我们也可以称它为或运算符。

let a:string|number;

这个的意思就是a变量既可以是string类型也可以是number类型。

let a:string|number;
a=1;
a='string';

所以我们可以给a即赋值字符串页也可以赋值数值。但是不能再是其他类型了。

再比如在函数中:

let add: (x: number | string) => number | string = function (x) {
    if (typeof x === "string") return x + "!";
    else return x + 1;
};

这样我们的add函数的参数就可以是字符串也可以是数值了。

而且联合类型常常用于type中:

type Person = {name:string,age:number}
type State = {name:string,state:string}

type Worker = Person | State;

那么现在Worker既包含Person的类型,也包含State的类型。

let worker1:Worker = {
    name:'xiaolei',
    age:18,
    state:'good'
}
let worker2:Worker = {
    name:'xiaoxu',
    age:18
}
let worker3:Worker = {
    name:'xiaowang',
    state:'good'
}
let worker4:Worker = {
    age:18,
    state:'good'
}  // 类型 "{ age: number; state: string; }" 中缺少属性 "name",但类型 "State" 中需要该属性。

我们发现声明这个Worker类型的变量时,既可以只赋值Person,也可以只赋值State,也可以两种的都赋值(但是不能每一个赋值一部分)。但是在使用的时候就有问题了:

console.log(worker1.age); //类型“State”上不存在属性“age”
console.log(worker2.state); // 类型“Person”上不存在属性“state”。
console.log(worker3.name);

最后只有worker3.name没有报错,这是因为在type中使用联合类型来定义类型,你只能取它们的共有属性

交叉类型

交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。它类似于接口的继承。

type Person = { name: string; age: number };
type State = { name: string; state: string };

type Worker = Person & State;

它相当于:

type Worker ={name: string; age: number;state: string};

但是交叉类型也有它自己的特性,那就是它赋值的时候必须全部赋值

let worker1: Worker = {
    name: "xiaolei",
    age: 18,
    state: "good"
};
let worker2: Worker = { //报错:缺少属性 "state",但类型 "State" 中需要该属性。
    name: "xiaoxu",
    age: 18
};
let worker3: Worker = { //报错:缺少属性 "age",但类型 "Person" 中需要该属性。
    name: "xiaowang",
    state: "good"
};
let worker4: Worker = { //报错:缺少属性 "name",但类型 "Person" 中需要该属性。
    age: 18,
    state: "good"
};

但是我们在使用的时候就可以取他们各自拥有的或者两者共有的

console.log(worker1.age);
console.log(worker1.state);
console.log(worker1.name);

此外我们可以把Worker类型的数据分别赋值给Person和State类型的数据:

let worker1: Worker = {
    name: "xiaolei",
    age: 18,
    state: "good"
};
let p1:Person = {
    name:'xiaoxu',
    age:18
}
let state:State = {
    name:'xiaoxu',
    state:'bad'
}
p1 = worker1;
state = worker1;

所以说我们可以把交叉类型认为是继承,因为使用交叉类型后新得到的类型是拥有了使用交叉类型的特性的,那么新类型也就包含继承前的类型,也就可以相互赋值了(后面断言的要求也是这个)。

此外,如果是两个相同的类型使用交叉类型也是可以的:

type Fn1 = (value:number)=>string
type Fn2 = (value:string)=>string

type Fn3 = Fn1 & Fn2;

这样的结果就是:

type Fn3 = (value:number|string)=>string

这也是type和interface的一个区别点:

interface Fn1 { //接口“Fn1”错误扩展接口“Fn2”。
    fn: (value: number) => string;
}
interface Fn2 {
    fn: (value: string) => string;
}
interface Fn1 extends Fn2 {} //报错

而使用type的话则是可以的:

type Fn3 = Fn1 & Fn2;