交叉类型(Intersection Types)
这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战
交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
简单而言可以认为交叉类型即是所有类型的并集操作
例如, Person & Loggable同时是 Person 和 Loggable。 就是说这个类型的对象同时拥有了这三种类型的成员
interface Foo {
name: string
}
interface Bar {
age: number
}
const bar: Foo & Bar = {
name: 'Klaus',
age: 24
}
联合类型(Union Types)
联合类型与交叉类型很有关联,但是使用上却完全不同,联合类型可以看成是多个类型的交集
联合类型表示一个值可以是几种类型之一。 我们用竖线( |)分隔每个类型,所以 number | string | boolean表示一个值可以是 number, string,或 boolean
如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员。
// value的类型为number或者string
function getLength(value: number | string) {
return typeof value === 'number' ?
value.toString().length : value.length
}
console.log(getLength(123)) // => 3
console.log(getLength('Klaus')) // => 5
// console.log(getLength(true)) // error
interface Fish {
swim: () => void,
name: string
}
interface Dog {
run: () => void,
name: string
}
let animal: Fish | Dog = {
swim() {
console.log('swim')
},
run() {
console.log('run')
},
name: 'animal'
}
// 联合类型只能访问联合类型上的公共属性或方法
console.log(animal.name)
// animal.swim() // error
// animal.run() // error
类型保护(Type Guards)
TS中为我们提供了联合类型,但是TS只会在编译阶段对我们的代码进行类型检测
所以我们在使用联合类型的时候,只能访问联合类型上边的公共属性和方法
如果我们需要使用联合类型上某一个具体类型,我们就不得不多次使用类型断言
为此TS提供了 类型保护机制让它成为了现实。 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。
类型谓词 is
// type is number 就是类型谓词
// 当isString方法的返回值是true的时候 type的类型就是number
// 否则就是string
function isString(type: string | number): type is number {
return typeof type === 'string'
}
typeof类型保护
function getLength(value: number | string) {
// TypeScript会将变量类型从number|string缩减为number类型
// 只要这个类型与变量的原始类型是兼容的。
if (typeof value === 'number') {
return value.toFixed()
} else {
return value.length
}
}
使用typeof类型保护有两个先决条件
在表达式typeof v === "typename"中
- 判断条件必须是全等,全不等,相等和不相等,不可以使用
includes之类的其它判等条件
function getLength(value: number | string) {
// (typeof value).includes('number')只会被TS认为是一条JS语句
// 并不会产生任何的类型保护机制
if ((typeof value).includes('number')) {
return value.toFixed() // error
} else {
return value.length // error
}
}
-
typename的类型只能是"number"
,"string","boolean"或"symbol"中的一种但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
instanceof类型保护
instanceof类型保护是通过构造函数来细化类型的一种方式
instanceof的右侧要求是一个构造函数
class User1 {
constructor(public name: string) {
this.name = name
}
}
class User2 {
constructor(public age: number) {
this.age = age
}
}
function getNameOrAge(user: User1 | User2) {
if (user instanceof User1) {
return user.name
} else {
return user.age
}
}
console.log(getNameOrAge(new User1('Klaus')))
console.log(getNameOrAge(new User2(24)))
null 和 undefined
TypeScript具有两种特殊的类型, null和 undefined
null和 undefined可以看成是其它任意类型的子类型,可以赋值给其它任意类型
但是在默认情况下,TS会开启--strictNullChecks标记
此时 null和 undefined不会作为其它任意类型的子类型,而是会作为单独类型进行使用
如果一个变量的确可以被赋值为null或undefined,那么需要使用联合类型
let str: string | undefined
str = 'Klaus'
str = undefined
使用了 --strictNullChecks,可选参数和可选属性会被自动地加上 | undefined
let user: {
name?: number
} = {}
// 此时name的类型为 number | undefined
user.name = undefined // success
// 此时num2的类型就是number | undefined
function sum(num1: number, num2?: number) {}
sum(1, undefined) // success
非空断言
由于可以为null的类型是通过联合类型实现,那么你需要使用类型保护来去除 null
function fun(param: string | null): string {
return param || "default";
}
如果编译器不能够去除 null或 undefined, 我们可以使用非空断言, 语法是添加 !后缀
例如: identifier!从 identifier的类型里去除了 null和 undefined
function fixed(name: string | null): string {
function postfix(epithet: string) {
// name一定不为null
// charAt(0) 取出name的第一个字符
return name!.charAt(0) + '. the ' + epithet; // ok
}
name = name || "Bob";
return postfix("great");
}
类型别名
类型别名会给一个类型起个新名字。
起别名不会新建一个类型,它创建了一个新 名字来引用那个类型。
// 使用type关键字定义类型别名
type Foo = () => void
const foo: Foo = () => { console.log('foo') }
foo()
同接口一样,类型别名也可以是泛型 (在别名声明的右侧传入),可以使用类型别名来在属性里引用自己
type User<T> = {
name: T,
friend?: User<T>
} // success
// 类型别名不能出现在声明右侧的任何地方, 只能在别名声明的后边
type arr = Array<T> // error
type LinkedList<T> = T & { next?: LinkedList<T> };
interface Person {
name: string;
}
let people: LinkedList<Person> = {
name: 'Klaus',
};
let s: string | undefined = people.name;
s = people.next?.name;
s = people.next?.next?.name;
s = people.next?.next?.next?.name;
接口 和 类型别名的区别:
- 接口创建了一个新的数据类型,可以在其它任何地方使用。 类型别名并不创建新的数据类型。
- 类型别名不能被
extends和implements(自己也不能extends和implements其它类型)