TS = JS + 类型系统
JS 可以对值进行加减运算,如果把 TS 的类型系统当作一门语言,TS 也同样可以对类型进行各种运算
联合类型(并集)
英文全称:union types

type A = { name: string } 表示 name 为 string 的所有对象
但不要错误地认为这些对象只有 name 这一个 key
比如对象 { name: 'ClariS'; age: 18 } 也是属于 A 类型的
即 A 类型的对象可以有 age,也可以没有 age
同理,type = { age: number } 表示 age 为 number 的对象,这些对象的 name 可以为空,也可以不为空
如何使用联合类型?
const f1 = (a: number | string) => {
'既不能把 a 当作 number'
'也不能把 a 当做 string'
'那么,怎么使用 a 变量呢?'
}
如果不拆开类型,就只能使用 number 和 string 共同拥有的方法或属性,比如 toString()
因此要使用联合类型,就得先区分类型,也就是进行类型收窄(Narrowing)
如何进行类型收窄?
使用 typeof
const f1 = (a: number | string) => {
if (typeof a === 'number') {
a.toFixed(2);
} else if (typeof a === 'string') {
parseFloat(a).toFixed(2);
} else {
throw new Error('Never do this');
}
};
typeof 的局限性:无法区分数组、日期、普通对象以及 null(返回值都是 "object")
使用 instanceof
const f1 = (a: Array<Date> | Date) => {
if (a instanceof Date) {
a.toISOString();
} else if (a instanceof Array) {
a[0].toISOString();
} else {
throw new Error('Never do this');
}
};
instanceof 的局限性:
- 不支持基础类型,
string、number、bigint、boolean、symbol、undefined - 不支持 TS 独有的类型
ts type Person = { name: string }; const f1 = (a: Person | Person[]) => { if (a instanceof Person) { // ^--- 报错:type 不能用作 value } else { throw new Error('Never do this'); } };
使用 in 操作符
type Person = { name: string };
const f1 = (a: Person | Person[]) => {
if ('name' in a) {
a; // Person
} else {
a; // Person[]
}
};
in 操作符的局限性:只适用于部分对象
比如当判断的两个对象存在相同的 key (存在相同 key,其中一个对象的 key 可能不存在),或不为键值对形式的对象(比如日期、正则、函数等)时,则无法使用 in 操作符来收窄类型
使用 JS 中判断类型的函数
const f1 = (a: string | string[]) => {
if (Array.isArray(a)) {
a.join('\n').toString();
} else if (typeof a === 'string') {
parseFloat(a).toFixed(2);
} else {
throw new Error('Never do this');
}
};
使用逻辑
const f1 = (a?: string[]) => {
if (a) {
a; // string[]
} else {
a; // undefined
}
};
const f2 = (a: string | number) => {
a = 1;
a; // number
};
const f3 = (x: string | number, y: string | boolean) => {
if (x === y) {
x; // string
y; // string
} else {
x; // string | number
y; // string | number
}
};
使用类型谓词 is
上述所有的类型收窄方法都是通过 JavaScript 来实现的,有没有区分类型的万全之法?
type Rect = { height: number; width: number };
type Circle = { center: [number, number]; radius: number };
// 推荐使用普通函数实现 isRect,而不使用箭头函数
function isRect(x: Rect | Circle): x is Rect {
return 'height' in x && 'width' in x;
}
const f1 = (a: Rect | Circle) => {
if (isRect(a)) { // `isRect(a)` 判断 a 是不是 Rect
a; // Rect
} else {
a; // Circle
}
};
优点:支持所有 TS 类型 缺点:需要自己实现,挺麻烦的
有没有更简单的办法?
可辨别联合
interface Circle { kind: "circle"; radius: number; }
interface Square { kind: "square"; sideLength: number; }
type Shape = Circle | Square
const f1 = (shape: Shape) => {
if (shape.kind === 'circle') {
shape // Circle
} else if (shape.kind === 'square') {
shape // Square
} else {
shape // never
}
}
让复杂类型的收窄变成简单类型的对比
对于类型 T = A | B | C | D | ...,有以下要求:
A、B、C、D...有相同属性kind或其他kind的类型是简单类型- 各类型中的
kind可区分
则称 T 为可辨别联合
一句话总结:具有同名、可辨别的简单类型的 key 的联合类型,称为可辨别联合
使用断言
可以使用 as 进行强制的类型收缩
interface Circle { kind: "circle"; radius: number; }
interface Square { kind: "square"; sideLength: number; }
type Shape = Circle | Square
const f1 = (shape: Shape) => {
(shape as Circle).radius = 4
}
any 等于所有类型的联合吗?
这里直接给出结论:any 不等于所有类型的联合
注意:这里的所有类型不包括
neverunknownanyvoid
用反证法可以证明:
- 只要类型发生了联合,就只能使用它们共同拥有的属性或方法(想要使用各自的方法就必须做类型收窄)
- 但是使用
any之后,不做类型收窄可以使用所有类型的方法 - 说明
any不是所有类型的联合
const f1 = (a: string | number) => {
a.toString();
a.toFixed(); // 报错
a.split(); // 报错
};
const f2 = (a: any) => {
a.toString();
a.toFixed();
a.split();
};
且 ts 的绝大部分规则对 any 不生效,但有一种特殊情况,any 无法赋值给 never
那什么等于所有类型的联合呢?
const f1 = (a: unknown) => {
if (a instanceof Date) {
a; // Date
}
};
type Rect = { height: number; width: number };
type Circle = { center: [number, number]; radius: number };
function isRect(x: Rect | Circle): x is Rect {
return 'height' in x && 'width' in x;
}
const f2 = (a: unknown) => {
if (isRect(a)) {
a; // Rect
}
};
可以看出,unknown 是可以收窄到任何类型的,因此,unknown 就是所有类型的联合
评论区
评论加载中...