TypeScript三部曲之基础篇

1,794 阅读19分钟

前言

TypeScriptJavaScript的超集,为JavaScript赋予了强类型语言的特性。在项目中使用TypeScript能够将错误提前暴露在开发阶段,并且TypeScript除保留JavaScript原有特性外还增加了很多强大的特性,如:接口泛型等等,为开发带来了极大的便利。前阵子在学习TypeScript过程中整理了不少笔记,在梳理过程中就产生了该文章,同时也希望让更多同学了解TypeScript,顺便帮自己加深记忆。

TypeScrip中的数据类型

TypeScript的数据类型,包含了JavaScript所有的数据类型。除此之外还提供了枚举元组anyvoid等类型,下面讲述在TypeScript中如何使用这些类型。

JavaScript中原有的数据类型

  • Boolean
// Boolean
let bool: boolean = true;
  • String
// String
let str: string = 'hello TypeScript';
  • Number
    • JavaScript一样,TypeScript里的所有数字都是浮点数
    • 除支持十进制、十六进制外,TypeScript还支持二进制、八进制
// Number
let decLiteral: number = 10;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
  • Array
    • Array分两种声明方式:
      • 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组
      • 第二种方式是使用数组泛型,Array<元素类型>
// 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组
let arr1: number[] = [1, 2, 3];

// 第二种方式是使用数组泛型,Array<元素类型>
let arr2: Array<number> = [1, 2, 3];

// 数组支持多种元素类型
let arr3: (number | string) = [1, '2', 3];
let arr4: Array<number | string> = [1, '2', 3];
  • Function
// Function
let add = (x, y) => x + y;
  • Object
// 可以这样定义,但访问、修改属性时会抛出错误,因为Object上不存在x、y属性。
let obj: Object = {x: 1, y: 2}; 
console.log(obj.x); // 类型“Object”上不存在属性“x”

// 完善定义
let obj1: {x: number, y: number} = {x: 1, y: 2};
console.log(obj1.x); // 1
  • Symbol
// Symbol
let syb: symbol = Symbol('hello');
  • null与undefined
// null与undefined
let null: null = null;
let undefined: undefined = undefined;

上面示例中我们在变量后跟类型的方式,被称之为:类型注解。使用类型注解的变量必须按照类型注解声明的类型来赋值,否则TypeScript会抛出错误。

TypeScript新增的数据类型

  • void
    • void代表没有返回值,或返回值为undefined,通常用来声明函数的返回值类型。
    • 如果一个变量被申明为void类型,那它只能被赋值为nullundefeated
// void
let add = (x: number, y: number): void => undefined; // 表示返回值为undefined
let unusable: void = undefined;
unusable = 'hello'; // ts会抛出错误,不能将类型“"hello"”分配给类型“void”
  • any
    • any可以被赋值为除never外的任何类型。
    • 注:可以使用any,但是不建议全部使用any来定义类型,这样使用TypeScript就没有任何意义了。
// any
let any: any = 1;
any = '1';
any = null;
  • never
    • never类型表示的是那些永不存在的值的类型
    • 返回never的函数必须存在无法达到的终点,比如:一个死循环的函数、一个总是抛出错的函数。
    • never只能被赋值为never自身。
// never
function loop(): never {
  while(true) {}
}

function error(message: string): never {
  throw new Error(message);
}
  • 元组
    • 元组类型表示一个已知元素数量和类型的数组,各元素的类型不必相同
    • 元组越界会抛出错误
// 元祖 tuple

let tuple: [number, string] = [1, '2'];
tuple[2] = 2; // 不能将类型“2”分配给类型“undefined”。
  • 枚举
    • 枚举分为两种类型:数字枚举字符串枚举
    • 枚举成员的值为只读类型,不能修改
    • 默认为数字枚举,从0开始,依次递增
    • 枚举成员类型分为两类:常量枚举计算型枚举
      • 常量枚举:常量枚举会在编译时计算出结果,已常量的方式保存。常量枚举赋值分以下三种情况:
        1. 没有初始值
        2. 对已有成员的引用
        3. 常量表达式
      • 计算型枚举:需要被计算的枚举成员,计算型枚举不会在编译时计算,会保留到执行阶段。
        1. 计算型枚举后的枚举成员必须要赋值
    • 需要注意的是,包含字符串的枚举中不能使用计算型枚举
    • 注:通常枚举会用于权限、状态判断。避免"魔术数字"的出现,提高可读性、可维护性。
// 数字枚举,默认从0开始,依次递增
enum Color {
  Red,
  Green,
  Blue
}

console.log(Color.Red) // 0
console.log(Color.Red) // 1
console.log(Color.Red) // 2

// 自定义数字枚举的值
enum Alphabet {
  A = 8,
  B,
  C = 2020
}

console.log(Alphabet.A) // 8
console.log(Alphabet.B) // 9
console.log(Alphabet.C) // 2020

// 类型保护
let year = Alphabet.C; 
year = 'today'; // 不能将类型“"today"”分配给类型“Alphabet”

// 数字枚举支持双向映射
console.log(Alphabet.A) // 8
console.log(Alphabet[8]) // A

// js实现双向映射
var Alphabet;
(function (Alphabet) {
    Alphabet[Alphabet["A"] = 8] = "A";
    Alphabet[Alphabet["B"] = 9] = "B";
    Alphabet[Alphabet["C"] = 2020] = "C";
})(Alphabet || (Alphabet = {}));

// 字符串枚举
enum Message {
  Fail = '失败',
  Success = '成功'
}

// 枚举成员类型
enum Library {
  // 常量枚举
  BookName,
  Year = 2020,
  Count = BookName + Year,
  // 计算型枚举
  Number = Math.random(),
  Size = '如何成为掘金V6'.length // 计算型枚举后的枚举成员必须要赋值
}

看完上面的内容,我们已经对TypeScript中的数据类型已经有一个大概的了解了。有同学可能会有疑问,如果我们需要描述一个复杂的数据类型该怎么编写类型注解呢?这就涉及到我们下面讲的内容接口,如何使用接口来描述一个复杂的数据类型。

接口

接口TypeScript中核心概念之一,主要用于约束对象、函数、类的结构

  • 如何声明一个接口?
    • 可使用interface关键字声明接口
  • 接口可以限制哪些属性?
    • 可选属性:x?: string,来约定参数是否可选
    • 只读属性:readonly x: number ,来约定参数是否只读
  • 接口分几种类型?
    • 对象类型
    • 函数类型
      • 函数的参数名不需要与接口里定义的名字相匹配,但要求对应位置上的类型是兼容的
    • 索引类型
    • 混合类型
  • 注:接口具有鸭式辩型法特性。
    • 什么是鸭式辩型法
      • 答:一只鸟如果走起来像鸭子,叫起来像鸭子,游起来像鸭子,那么这只鸟就能被当成一只鸭子。这就是鸭式辨型法,下面我们会举例说明。

接口的几种类型

/**
 * 对象类型接口
 */

// 可选属性与只读属性
interface Person {
  name: string;
  readonly age: number;
  sex: 'boy' | 'girl',
  hobby?: string[]
}

let Tom: Person = {
  name: 'Tom',
  age: 3,
  sex: 'boy'
};
Tom.age = 1; // Cannot assign to 'age' because it is a read-only property.

let Jerry: Person = {
  name: 'Jerry',
  age: 3,
  sex: 'boy',
  hobby: ['和汤姆一起快乐的玩耍']
}

/**
 * 鸭式辩型法举例
 * renderList传入的参数只要满足Result接口的约定即可,多余的code、msg不会影响
 * 这也是我们提到的:一只鸟如果走起来像鸭子,叫起来像鸭子,游起来像鸭子,那么这只鸟就能被当成一只鸭子。
 */
interface ListItem {
  id: number;
  name: string
}

interface Result {
  list: ListItem[]
}

function renderList(result: Result) {
  result.list.forEach((item: ListItem) => {
    console.log(item.id, item.name)
  })
}

const data = {
  list: [{
    id: 1,
    name: '小红'
  }, {
    id: 2,
    name: '小白'
  }],
  code: 1,
  msg: ''
};
renderList(data);

/**
 * 函数类型接口
 * 函数的参数名不需要与接口里定义的名字相匹配,但要求对应位置上的类型是兼容的
 */
let add: (x: number, y: number) => number;

interface Add {
    (x: number, y: number): number;
}
// 上面这两种定义式等价的

// 函数的参数名不需要与接口里定义的名字相匹配
let add1: Add = (j: number, k: number) => j + k;

// 但要求对应位置上的类型是兼容的
let add2: Add = (j: string, k: number) => j + k; // 不能将类型“(j: string, k: number) => string”分配给类型“Add”。参数“j”和“x” 的类型不兼容。

/**
 * 索引类型接口
 * 索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。
 * 当不确定接口中属性个数时可以使用索引类型接口
 * 索引类型分字符串索引和数字索引
 * 字符串与数字索引可以同时使用,但数字索引必须是字符串索引返回的子类型
 */

// 数字索引
interface StringArray {
  [index: number]: string;
}
const myArra1: StringArray = ['1', 2]; // 不能将类型“number”分配给类型“string”。
const myArray: StringArray = ['1', '2'];

// 字符串索引
interface ListItem {
  id: number;
  name: string;
  [x: string]: any;
}

// 这里我们使用string去索引any
let list: ListItem[] = [{
  id: 1,
  name: 'aa',
  year: '2019'  
}]

// 字符串与数字索引可以同时使用,但数字索引必须是字符串索引返回的子类型
interface ArrayItem {
  [x: string]: string;
  [y: number]: number; // 数字索引类型“number”不能赋给字符串索引类型“string”
}

interface ArrayItem1 {
  [x: string]: string;
  [y: number]: string;
}

/**
 * 混合类型接口
 * 混合类型接口是函数类型接口与对象类型接口的集合
 */
interface Lib {
  (): void;
  version: string;
  doSomething(): void;
}

let lib: Lib = (() => {}) as Lib;
lib.version = '1.0.0';
lib.doSomething = () => {}; 

类型别名与类型断言

  • 什么是类型别名?
    • 通常我们会使用interface声明接口,但类型别名也能达到同样的作用,甚至更方便简洁。
      • 如何声明类型别名?
        • 类型别名使用type关键字声明。
  • 什么是类型断言?
    • 有时候你会比TypeScript更清楚数据的类型,这时候就能使用类型断言,表示你清楚该数据的类型格式。
      • 如何使用类型断言
        1. 与JSX一致,使用<类型>的方式来声明类型断言。
        2. 使用as进行类型断言。
        3. 注:第一种方式在React中会与JSX冲突,推荐统一使用第二种方式。
// 使用接口声明一个函数类型接口
interface Add {
  (x: number, y: number): number
}

// 使用类型别名声明一个函数类型接口
type Add = (x: number, y: number) => number;

// 上面两种声明方式等价

/**
 * 类型别名的一些小技巧
 * 在React中初始化state,设置类型比较麻烦
 * 如果使用类型别名就很方便了
 */

const initialState = {
  page: 1,
  pageCount: 15,
  list: [{
    id: 1,
    name: 'Tom'
  }]
};

type State = typeof initialState;

type State1 = {
    page: number;
    pageCount: number;
    list: {
        id: number;
        name: string;
    }[];
}
// State与State1两者等价

/**
 * 类型断言
 * 类型断言有两种声明方式:1. 与JSX一般声明,2. 使用as关键字声明
 * 在React中请使用第二种方式,同时也推荐使用第二种方式
 */

interface Obj {
  x: number;
  y: number;
}
let obj = {};
obj.x = 1; // 类型“{}”上不存在属性“x”

// 1. 如JSX一般声明
let obj1 = <Obj>{};
obj1.x = 1;

// 2. 使用as关键字声明
let obj2 = {} as Obj;
obj2.x = 1;

函数

TypeScript中的函数行为基本与JavaScript保持一致,不同的是TypeScript支持以下功能:

  1. 支持约束参数类型、数量
    • 函数必须按照约定参数类型与数量传入参数,否则会抛出错误。
  2. 支持定义函数返回值的类型
    • 表示函数是否存在返回值、以及返回值的类型
  3. 支持可选参数
    • 可选参数必须位于必选参数之后
  4. 支持函数重载
    • TypeScript规定必须在类型最宽泛的版本中实现重载,你可以理解为在类型最为宽泛的版本中需要支持之前版本的所有类型。
// 约束参数类型、数量
function add(x: number, y: number) {
  return x + y;
};
add(1, 2); // 3
add(1, '2'); // 类型“"2"”的参数不能赋给类型“number”的参数
add(1, 2, 3); // 应有 2 个参数,但获得 3 个。

/**
 * 可选参数与联合类型
 * 可选参数除参数约定类型外,还会默认支持undefined
 * 可选参数都是联合类型
 * 联合类型是指使用“|”设置支持多种类型,后续内容会详细讲解
 */
function add1(x: number, y?: number) {
  return y ? x + y : x;
}
add1(1); // 1
add1(1, 2); // 3

// 默认参数
function add2(x: number, y: number = 2020) {}

// 剩余参数
function add3(x: number, ...rest: number[]){
  return rest.reduce((pre: number, cur: number) => pre + cur, x);
}
add2(1, 2, 3, 4, 5, 6); // 21

/**
 * 函数重载
 * TypeScript规定必须在类型最宽泛的版本中实现重载,你可以理解为在类型最为宽泛的版本中需要支持之前版本的所有类型。
 */
function add4(...rest: number[]): number;
function add4(...rest: string[]): string;
function add4(...rest: any) {
  let first = rest[0]
  if (typeof first === 'string') {
    return rest.join(' ')
  }
  if (typeof first === 'number') {
    return rest.reduce((pre: number, cur: number) => pre + cur)
  }
}

add4(2020, 1, 17) // 2038
add4('hello','TypeScript') // hello TypeScript
add4({x: 1, y: 2}) // 类型“{ x: number; y: number; }”的参数不能赋给类型“string”的参数

JavaScript本身是没有类概念,在ECMAScript 6之前都是使用函数与基于原型的继承创建可复用组件。 JavaScript在ECMAScript 6引入了class关键字,可用于基于类的面向对象编程。但是class的本质还是函数+原型,此处不多做讲解。TypeScript的类包含JavaScript的类,除此之外还新增类一些新特性如:成员修饰符抽象类多态等等。

继承与成员修饰符

  • TypeScript有哪些成员修饰符以及特性?
    • public,公共成员
      • 特性:公共成员都能访问
    • prviate,私有成员
      • 特性:私有成员不能被外部调用
    • protected,受保护成员
      • 特性:只能在类及其子类中访问。
    • readonly
      • 特性:只读属性,不可被更改
    • static
      • 特性:只能通过类名访问,不能通过子类访问
  • TypeScript类的属性必须具有初始值。
  • TypeScript类属性默认为public,公共成员
/**
 * 继承与成员修饰符
 */

interface PersonBase {
  name: string;
  age: number;
  sex: 'boy' | 'girl';
}

class Person {
  name: string;
  age: number;
  readonly sex: string; // 只读属性,不可被更改
  constructor({ name, age, sex }: PersonBase) {
    this.name = name;
    this.age = age;
    this.sex = sex;
  }
}

class Man extends Person {
  private wife: string; // 私有成员不能被外部调用
  protected propertyList?: string[]; // 受保护成员,只能在类及其子类中访问。
  static weight: number = 120; // 只能通过类名访问,不能通过子类访问
  constructor(personBase: PersonBase) {
    super(personBase);
    this.wife = 'Jilly';
    this.propertyList = ['house', 'car'];
  }

  private getWife() {
    return this.wife;
  }

  getWeight() {
    return this.weight; // Property 'weight' is a static member of type 'Man'
  }
}

class Son extends Man {
  constructor(personBase: PersonBase) {
    super(personBase);
  }
}

const Tom = new Man({
  name: 'Tom',
  age: 18,
  sex: 'boy'
});
const Child = new Son({
  name: 'child',
  age: 3,
  sex: 'boy'
});
Child.sex = 'girl'; // Cannot assign to 'sex' because it is a read-only property.
console.log(Tom.propertyList); // 属性“propertyList”受保护,只能在类“Man”及其子类中访问。
console.log(Child.getWife()); // 属性“getWife”为私有属性,只能在类“Man”中访问。
console.log(Man.weight); // 只能通过类名访问,不能通过子类访问

抽象类与多态

  • 什么是抽象类?
    • 抽象类通常作为基类,将多个事物中的共性抽离,方便其他派生类继承。
  • 抽象类有什么特点?
    • 只能被继承,不能被实例化。
  • 如何创建抽象类?
    • 抽象类使用abstract关键字定义抽象类与抽象方法。
  • 什么是多态?
    • 在父类中定义,在子类中可以有不同的实现,此为多态。
/**
 * 抽象类与多态
 * 抽象类只能被继承,不能被实例化。
 * 抽象类中的抽象方法不包含具体实现并且必须在派生类中实现
 */
 
// 实现一个抽象类
abstract class Animal {
  eat() {
    console.log('eat');
  }
  // 实现一个抽象方法,抽象方法不包含具体实现并且必须在派生类中实现
  abstract sleep(): void;
}

const pig = new Animal(); // 无法创建抽象类的实例

class Dog extends Animal {
  name: string;
  constructor(name: string){
    super();
    this.name = name;
  }

  sleep() {
    console.log('dog sleep')
  }
}

let dog = new Dog('wang wang');
dog.sleep()

// 非抽象类“Cat”不会实现继承自“Animal”类的抽象成员“sleep”
class Cat extends Animal {
}

类与接口

  • 接口与接口之间可相互继承,使用extends关键字继承。
  • 继承多个接口需要用“,”分割
  • 类使用接口时,需要使用implements关键字
  • 接口与类可相互继承
/**
 * 类与接口
 * 接口与接口之间可相互继承,使用extends关键字继承。
 * 继承多个接口需要用“,”分割
 * 类使用接口时,需要使用implements关键字
 * 接口与类可相互继承
 */

interface Animal {
  eat(): void;
}

// 接口继承接口
interface Felidae extends Animal {
  run(): void;
}

interface Pets {
  cute: boolean;
}

// 同时继承多个接口
interface Cat extends Felidae, Pets {}

// 类使用接口
class Tom implements Cat {
  cute = true;
  eat(){};
  run(){};
}

// 接口继承类
interface Jack extends Tom {}

泛型

  • 什么是泛型?
    • 不预先确认数据格式,具体的类型格式在使用时才能确认。
  • 泛型的优化:
    1. 函数和类可以轻松支持多重属性,增强程序的可扩展性
    2. 不必写多条函数重载,冗长的联合类型声明,增强代码可读性。
    3. 灵活控制类型之间的约束。
  • 注:泛型不能用于类的静态成员
/**
 * 泛型
 * 不预先确认数据格式,具体的类型格式在使用时才能确认。
 * 泛型不能用于类的静态成员
 */

// 一个简单的泛型函数
function log<T>(msg: T): T {
  return msg;
}

// 实现一个泛型接口
interface Log {
  <T>(msg: T): T
}

// 上面两种实现方式等价

// 两种使用方式
log<string>('hello'); // 第一种,告诉泛型函数传入的参数是什么类型
log('hello'); // 第二种,通过TypeScript类型推断来确定类型

let myLog: Log = <T>(msg: T) => msg;
myLog<string>('hello');
myLog('hello');

class Cat<T> {
  static eat(name: T) {} // 静态成员不能引用类类型参数
}

类型保护机制

类型保护机制,能够保证TypeScript变量在特定的区块内属于特定的类型,因此,在类型保护区块中,可以放心的引用对应类型的属性和方法。

/**
 * 类型保护机制
 * 类型保护机制能确定在指定区块内,可以使用对应类型的方法和属性
 * 可使用instanceof、typeof、in、hasOwnProperty或组合判断等方式来确定区块
 */
class Pig {
  edible: boolean = true;
}

class Cat {
  cute: boolean = true;
}

function getAnimal(animal: Pig | Cat) {
  const Animal = animal instanceof Pig ? new Pig() : new Cat();
  if (Animal instanceof Pig) {
    console.log(Animal.edible); // true
  } else {
    console.log(Animal.cute); // true
  }
}

高级类型

交叉类型与联合类型

  • 什么是交叉类型
    • 将多个类型合并为一个类型,可以认为是多个类型的集合。
  • 如何实现交叉类型
    • 使用&将多个类型合并为一个类型。
  • 什么是联合类型
    • 将变量设置多种类型,赋值时可以根据设置的类型来赋值。
  • 如何实现联合类型
    • 使用|来设置多个类型, 赋值时可以根据设置的类型来赋值。
/**
 * 交叉类型与联合类型
 * 交叉类型会将多个类型合并为一个类型,可以认为是多个类型的集合。
 * 交叉类型使用&将多个类型合并为一个类型。
 * 联合类型会将变量设置多种类型,赋值时可以根据设置的类型来赋值。
 * 联合类型使用|来设置多个类型, 赋值时可以根据设置的类型来赋值。
 */

interface Dog {
  run: () => void;
}

interface Cat {
  jump: () => void;
}

// 交叉类型
let pet: Dog & Cat = {
  run: () => {},
  jump: () => {}
};

// 联合类型
let Sex: 'boy' | 'girl';
let Status: 'success' | 'fail' | 'loading'

索引类型

  • 索引类型让静态检查能够覆盖到类型不确定(无法穷举)的”动态“场景。 例如,一个常见的JavaScript模式是从对象中选取属性的子集。
  • keyof T索引类型查询操作符,T你可以理解为任何类型与值。
  • T[K]索引访问操作符,与获取对象相似,存在返回值的类型,不存在会抛出错误。
/**
 * 索引类型
 */

let obj = {
  a: 1,
  b: 2,
  c: 3
};

// keyof T
interface Obj {
  a: number;
  b: string;
}
let key: keyof Obj; // "a" | "b"

// T[K]
let value: Obj['a']; // number

/**
 * T extends U
 * 这个函数的意思是,从K[]中找到T包含的索引,返回类型为T[K]
 * 简单的来说就是,从keys中找到obj中包含的值,keys如果传入obj中不存在的值就会抛出错误
 */

function getValue<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
  return keys.map(key => obj[key]);
}
console.log(getValue(obj, ['a', 'b'])); // [1, 2]
console.log(getValue(obj, ['a', 'f'])); // 不能将类型“string”分配给类型“"a" | "b" | "c"”  [1, undefined]

映射类型

  • 什么是映射类型
    • 从旧类型中创建新类型的一种方式。新的类型以相同的形式去转换旧的类型,比如可以令每个属性成为readonly或者可选的。
/**
 * 映射类型
 * 从旧类型中创建新类型的一种方式。新的类型以相同的形式去转换旧的类型,比如可以令每个属性成为readonly或者可选的。
 * TypeScript自身提供了很多种映射类型,如:Readonly、Partial、Pick、Record等等
 * 有兴趣的同学可以查看 node_modules/typescript/lib/lib.es5.d.ts 文件
 */

interface Obj {
  a: number;
  b: string;
  c: boolean;
}

// 只读
type ReadonlyObj = Readonly<Obj>

// 可选
type PartialObj = Partial<Obj>

// 抽离指定属性
type PickObj = Pick<Obj, 'a' | 'b'>

type RecordObj = Record<'x' | 'y', Obj>

条件类型

  • 什么是条件类型
    • 顾名思义就是根据条件来返回类型。
  • TypeScript 2.8在lib.d.ts里增加了一些预定义的有条件类型:
    • Exclude<T, U> -- 从T中剔除可以赋值给U的类型。
    • Extract<T, U> -- 提取T中可以赋值给U的类型。
    • NonNullable<T> -- 从T中剔除nullundefined
    • ReturnType<T> -- 获取函数返回值类型。
    • InstanceType<T> -- 获取构造函数类型的实例类型。
/**
 * 条件类型
 */
// T extends U ? X : Y;

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<number>; // number
type T2 = TypeName<string[]> // object

// (A | B) extends U ? X : Y;
type T3 = TypeName<number | string[]>; // "number" | "object"

// Diff的意思是从T中找出U中不存在的值,存在返回never,不存在返回该值
type Diff<T, U> = T extends U ? never : T;
type T4 = Diff<'a' | 'b' | 'c', 'a' | 'e'>
// Diff <'a', 'a', 'e'> | Diff <'b', 'a', 'e'> | Diff <'c', 'a', 'e'>
// never | 'b' | 'c'
// 'b' | 'c'

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

// Exclude<T, U>
// NotNullable<T>
// Extract<T, U>
type T6 = Extract<'a' | 'b' | 'c', 'a' | 'e'>; // a

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

结语

到此TypeScript三步曲之基础篇就结束了,感谢大家的阅读,如有错误欢迎斧正。

同时2020年的第一个flag也实现了,在2020年1月20日回家前输出该文章,虽然花了很多时间和精力,但也让我更深入的了解了TypeScript并且加深了印象。后续的TypeScript三步曲之配置篇TypeScript三步曲之工程篇预计3-4月输出。

明天就回家过年了,在此提前祝各位掘友:新年快乐、身体健康、事事如意。