Skip to content
目录

基础篇-高级类型

主要介绍:交叉类型联合类型索引类型映射类型条件类型

交叉类型

交叉类型是将多个类型合并为一个总的类型,它包含了多个类型的所有特性,类似于编程逻辑中的 且 操作.

语法: T1 & T2 & ...

js
interface Bird {
  fly(): void;
}
interface Dog {
  run(): void;
}

class Animal {
  fly() {
    console.log('我会飞');
  }
  run() {
    console.log('我会跑');
  }
}

let animal: Bird & Dog = new Animal();
animal.fly(); //我会飞
animal.run(); //我会跑

联合类型

语法:T1 | T2 | ...

联合类型是取多个类型中的其中之一,只要满足了其中一个类型,就认为类型兼容。联合类型类似于编程逻辑中的 或 操作。
我们用竖线|分隔每个类型,所以number | string | boolean表示一个值可以是number,string,或boolean

js
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
js
let myFavoriteNumber: string | number;
myFavoriteNumber = true;
//   Type 'boolean' is not assignable to type 'number'.

访问联合类型共有的属性或方法

我们只能访问此联合类型的所有类型里共有的属性或方法

js
function getLength(something: string | number): number {
  return something.length;
}
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.

length 不是 string 和 number 的共有属性,所以会报错。 访问 string 和 number 的共有属性是没问题的:

js
function getString(something: string | number): string {
  return something.toString();
}

联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:

js
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // 5
//myFavoriteNumber 被推断成了 string,访问它的 length 属性不会报错
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // 编译时报错
//myFavoriteNumber 被推断成了 number,访问它的 length 属性时就报错了

在看下面的列子,只允许共有的方法:

js
interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet(): Fish | Bird {
    // ...
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors

类型断言 as

有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。

通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript 会假设你,程序员,已经进行了必须的检查。 类型断言有两种形式。 其一是“尖括号”语法:

ts
let someValue: any = 'this is a string';

let strLength: number = (<string>someValue).length;

另一个为as 语法:

ts
let someValue: any = 'this is a string';

let strLength: number = (someValue as string).length;

两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在 TypeScript 里使用 JSX 时,只有 as 语法断言是被允许

正确的做法

ts
// 可以使用类型断言,将 something 断言成 string
function getLength(something: string | number): number {
  if ((<string>something).length) {
    return (<string>something).length;
  } else {
    return something.toString().length;
  }
}

错误的做法

ts
// 只能访问此联合类型的所有类型里共有的属性或方法
function getLength(something: string | number): number {
    return something.length;
}

类型别名 type

结合前面提到的类型别名,这里可以用

type 为性别创建一个别名类型,减少冗余。

js
type Gender = "male" | "female";

interface Person {
    name: string;
    gender: Gender;
}

function sayName(person: Person) {
    console.log(person);
}

const tom = {
    name: "tom",
    gender: "male" as Gender
};

sayName(tom);

type 与 interface

  1. type:不是创建新的类型,只是为一个给定的类型起一个名字。type 还可以进行联合、交叉等操作,引用起来更简洁
  2. interface:创建新的类型,接口之间还可以继承、声明合并,如果可能,建议优先使用 interface。
  3. 混合接口一般是为第三方类库写声明文件时会用到,很多类库名称可以直接当函数调用,也可以有些属性和方法。例子可以看一下@types/jest/index.d.ts 里面有一些混合接口。
  4. 用混合接口声明函数和用接口声明类的区别是,接口不能声明类的构造函数(既不带名称的函数),但混合接口可以,其他都一样。

相同点:

  • 都可以描述一个对象或者函数

interface

js
interface User {
  name: string
  age: number
}

interface SetUser {
  (name: string, age: number): void;
}

type:

js
type User = {
  name: string
  age: number
};
type SetUser = (name: string, age: number)=> void;

都允许拓展(extends)

interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface 。 虽然效果差不多,但是两者语法不同。

interface extends interface

js
interface Name {
  name: string;
}
interface User extends Name {
  age: number;
}

type extends type

js
type Name = {
  name: string,
};
type User = Name & { age: number };

interface extends type

js
type Name = {
  name: string,
};
interface User extends Name {
  age: number;
}

type extends interface

js
interface Name {
  name: string;
}
type User = Name & {
  age: number,
};

不同点

type 可以而 interface 不行

  • type 可以声明基本类型别名,联合类型,元组等类型
js
  // 基本类型别名
type Name = string

// 联合类型
interface Dog {
    wong();
}
interface Cat {
    miao();
}

type Pet = Dog | Cat

// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]

  • type 语句中还可以使用 typeof 获取实例的 类型进行赋值
js
// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type B = typeof div;
  • 其他骚操作
js
type StringOrNumber = string | number;
type Text = string | { text: string };
type NameLookup = Dictionary<string, Person>;
type Callback<T> = (data: T) => void;
type Pair<T> = [T, T];
type Coordinates = Pair<number>;
type Tree<T> = T | { left: Tree<T>, right: Tree<T> };

interface 可以而 type 不行

interface 能够声明合并

js
interface User {
  name: string
  age: number
}

interface User {
  sex: string
}

/*
User 接口为 {
  name: string
  age: number
  sex: string
}
*/

一般来说,如果不清楚什么时候用 interface/type,能用 interface 实现,就用 interface , 如果不能就用 type 。其他更多详情参看官方规范文档

类型推论

当类型没有给出时,TypeScript 编译器利用类型推论设置类型。

js
let uname = 'hello TS';
uname = 123;
console.log(uname); //Type '123' is not assignable to type 'string
//等价于
let uname: string = 'hello TS';
uname = 7;

如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:

js
let uname;
uname = 'hello TS';
uname = 7;

this 类型

多态的 this 类型表示的是某个包含类或接口的子类型

实例

js
class Counter {
  constructor(public count: number = 0) {}
  public add(value: number) {
    this.count += value;
    return this;
  }
  public min(value: number) {
    this.count -= value;
    return this;//返回实例实现链式调用
  }
}
const counter1 = new Counter(10);
console.log(counter1.add(2).min(3));

继承子类的方法链式调用

js
class CounterChild extends Counter {
  constructor(public count: number = 0) {
    super();
  }
  public pow(value: number) {
    this.count = this.count ** value;
    return this;
  }
}
const counter2 = new CounterChild(10);
console.log(
  counter2
    .pow(2)
    .add(1)
    .min(22)
);

索引类型 keyof

使用索引类型,编译器就能够检查使用了动态属性名的代码

ts
interface Person {
  name: string;
  age: number;
  grender: string;
}
//根据key查找对应的值
function getValues(obj: Person, key: string): any {
  return obj[key];
}

let student: Person = {
  name: '小明',
  age: 12,
  grender: '一年级',
};
const value = getValues(student, 'name');
//key只能取接口person中的key,需要对key进行约束
type PersonKey = keyof Person;
function getValues(obj: Person, key: PersonKey) {
  return obj[key];
}

当然也可以使用泛型进行约束

js
 function getValues<T, U extends keyof T>(obj: T, key: U) {
    return obj[key];
  }

索引访问操作符 T[K] 对象 T 的属性 K 代表的类型

ts
interface Person {
  age: number;
  name: string;
}
type value: Person["age"]; // type value: number

映射类型

内置类型别名

js
interface Person {
  name: string;
  age: number;
  grender: string;
}

Readonly 只读

可以讲一个旧的类型生成一个新的类型,比如把一个类型中的所有属性设置成只读。

ts
// 自定义接口所有属性设置成只读
type ReadonlyType<T> = {
  readonly [K in keyof T]: T[K];
};
const student2: ReadonlyType<Person> = {
  name: '小花',
  age: 12,
  grender: '三年级',
};
console.log(student2);
student2.name = '小菜'; //报错,只读属性

//内置 Readonly
type ReadonlyType2 = Readonly<Person>;

Partial 可选

js
//自定义设置为可选属性
type StudentType<T> = {
  [key in keyof T]?: T[key];
};
const student: StudentType<Person> = {
  name: '小明',
};
console.log(student);

//内置可选类型
type PartialType = Partial<Person>;
const student1: PartialType = {
  name: '小明',
};

Pick 挑选

ts
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};
//实例 抽取Obj a b 两种属性类型
type PickObj = Pick<Obj, 'a' | 'b'>;
//实例:
//提取某个,提取多个 |
type OneType = Pick<Person, 'age'>;
const studentNew1: OneType = {
  age: 123,
};
const studentNew2: OneType = {
  name: '小华',
};
console.log(studentNew1);
console.log(studentNew2); //报错

//设置x y 类型为Obj 对象任意值

type Record<K extends keyof any, T> = {
  [P in K]: T;
};
type RecordObj = Record<'x' | 'y', Obj>;

ts 还有更多内置的映射类型,路径在 typescript/lib/lib.es5.d.ts 内提供参考。

内置工具类型

掌握 TS 这些工具类型,让你开发事半功倍

条件类型

形式为 T extends U ? X : Y,如果类型 T 可以赋值为 U 结果就为 X 反之为 Y

ts
type TypeName<T> = T extends string
  ? 'string'
  : T extends number
  ? 'number'
  : T extends boolean
  ? 'boolean'
  : T extends undefined
  ? 'undefined'
  : T extends Function
  ? 'function'
  : 'object';

type T1 = TypeName<string>; // type T1 = "string"
type T2 = TypeName<string[]>; // type T2 = "object"

(A | B) extends U ? X : Y 形式,其约等于 (A extends U ? X : Y) | (B extends U ? X : Y)

ts
type T3 = TypeName<string | number>; // type T3 = "string" | "number"

利用该特性可实现类型过滤。

ts
type Diff<T, U> = T extends U ? never : T;

type T4 = Diff<'a' | 'b', 'a'>; // type T4 = "b"

// 拆解
// Diff<'a', 'a'> | Diff<'b', 'a'>
// never | 'b'
// 'b'

根据 Diff 再做拓展。

ts
type NotNull<T> = Diff<T, undefined | null>;

type T5 = NotNull<string | number | undefined | null>; // type T5 = string | number

以上 DiffNotNull 条件类型官方已经实现了。

TIP

Exclude<T, U> 等于 Diff<T, U>

NonNullable<T> 等于 NotNull<T>

还有更多的官方提供的条件类型,可供大家参考。

ts
// Extract<T, U>
type T6 = Extract<'a', 'a' | 'b'>; // type T6 = "a"

// ReturnType<T>
type T7 = ReturnType<() => string>; // type T7 = string