1.数据类型

值类型

提示

  • 概念:对应 JavaScript 中的基本类型。注意默认小写数据类型为 TypeScript 类型、大写数据类型为 JavaScript 类型
  • undefinednull 的关系:
    • undefinednull 在 TypeScript 中是所有类型的子类型。
    • null 在 ES 标准中是 引用类型
// 1.字符串 string
const str: string = 'abc'

// 2.数字 number
const num1: number = 6 // 十进制
const num2: number = 0b1010 // 二进制
const num3: number = 0o744 // 八进制
const num4: number = 0xf00d // 十六进制

// 3.布尔 boolean
const bool: boolean = true

// 4.符号 symbol (ES6)
const symb: symbol = Symbol('abc')

// 5.大整数 bigint (ES10)
const bigInt: bigint = 12n

// 6.未定义 undefined
const und: undefined = undefined

// 7.空值 null
const nul: null = null

引用类型

提示

  • 概念:元祖 Tuple 起源于函数式编程,是一个带有限定条件的数组;可以对元祖进行越界操作,但元素类型必须符合限定条件
// 1.对象 object
const obj1: object = { a: '1' }
const obj2: object = [1, 2]
const obj3: object = ['a', 1]

// 2.数组 Array
const arr1: number[] = [1, 2]
const arr2: Array<number> = [3, 4]

// 3.只读数组 ReadonlyArray
const arr1: readonly string[] = ['1', '2']
const arr2: ReadonlyArray<string> = ['3', '4']

// 4.类数组 ArrayLikeObject
function arrayLikeObject() {
  // arguments
  arguments.length // 可以调用数组的 length 属性
  arguments.forEach // 不可以调用数组的 forEach 函数
  // htmlCollection
  let htmlCollection: HTMLCollection
}

// 5.元祖 Tuple
const tup: [string, number] = ['a', 1]
tup.push('true') // 可以对元祖push,但必须是元祖声明的类型

顶级类型

提示

  • unknownany 的区别:
    • 相同点:值可以被任意修改。
    • 不同点:any 等价于 JavaScript 的变量类型; unknown 被确定是某个类型之前,不能进行实例化、getter、函数执行等操作。
// 1.任何 any
let an: any
an = '123'
an = 123 // 值可以被任意修改
an.test(1) // 函数执行不报错

// 2.未知 unknown
let un: unknown
un = '123'
un = 123 // 值可以被任意修改
un.test(1) // 函数执行报错

底部类型

提示

  • never 代表函数某个阶段永远不会执行,也不可能存在返回值。
  • void 代表函数可以正常执行,仅没有返回值或不明确的返回值。
// 1.从不执行 never
function test1(): never {
  throw new Error() // 异常抛出,后续永远不会执行
}
function test2(): never {
  while (true) {} // 死循环,后续永远不会执行
}

// 2.空 void
function test3(): void {}
function test4(): void {
  return undefined
}
function test5(): void {
  return null
}

2.接口和类型定义

interface 接口

提示

  • 概念:interface 针对对象的形状 (shape) 进行描述,也叫 duck typing (鸭子类型)。
// 1.接口
interface IPerson {
  name: string // 普通属性:属性必须存在
  age?: number // 可选属性:属性可以不存在
  readonly gender: 'male' | 'female' // 只读属性:只能在创建时被赋值,后续使用不得修改
  info: (message: string) => void // 函数属性
  [propName: string]: any // 索引签名属性
  new (): string // 实例化属性:构造函数返回一个字符串
}

const zxy: IPerson = {
  name: 'zxy',
  age: 18,
  gender: 'male',
  info(message) {},
  test: Symbol('a')
}
const zxyString = new zxy()

// 2.继承接口
// 单继承
interface ITimeMan extends IPerson {
  time1: number
}
// 多继承
interface ITimeWoman extends IPerson, ITimeMan {
  time2: number
}

const timeWoman: ITimeWoman = {
  name: 'a',
  age: 18,
  gender: 'male',
  info(message) {},
  test: Symbol('a'),
  time1: 1130, // 同时拥有 IPerson 和 ITimeMan 两个接口的属性
  time2: 1230
}

type 类型定义

提示

  • 概念:类似 interface,区别在于扩展了 简单类型、字面量枚举类型、可辨别类型定义、工具类型
// 1.类型定义
type TypeDef1 = {
  name: string
  age: number
}
type TypeDef2 = (x: number, y?: number) => number

// 2.字面量类型枚举
type TypeDef3 = 'top' | 'bottom' | 'left' | 'right'

// 3.可辨别类型定义:调用方可以根据 actions 的不同匹配不同的类型
type TypeDef4 =
  | {
      actions: 'get'
      name: string
      age: number
    }
  | {
      actions: 'post'
      name: string
      id: number
    }

// 4.工具类型:遍历 T 上的 key,将其转换为可选属性
type OptionalType<T> = { [K in keyof T]?: T[K] }

对比

类型定义种类扩展方式重复声明合并泛型
interface 接口对象类型、函数类型extends 继承✔️✔️
type 类型定义对象类型、函数类型、简单类型、字面量枚举类型、可辨别类型、工具类型|、& 多类型融合✔️

3.多类型融合

| 联合类型(或)

提示

  • 概念:指定某个变量为多个类型其中之一。
let union: string | number = '1'
union = 1

联合类型的变量 ts 只会提示所有类型的公共属性,调用某个类型的特有属性时会报错。解决方法如下:

  • as 类型断言

    function testUnion(union: string | number) {
      // 1.类型断言:通过 as 断言为 string 类型,检测变量上是否存在 string 实例的属性
      if ((union as string).length) {
        // 断言为 string 类型
        ;(union as string).length
      } else {
        // 断言为 number 类型
        ;(union as number).toFixed(1)
      }
    }
    
  • in 类型存在

    function testUnion(
      union: { a: string; b: number } | { a: string; c: string[] }
    ) {
      if ('b' in union) {
        // 推导为接口1类型
        union.b.toFixed(1)
      } else {
        // 推导为接口2类型
        union.c.push('1')
      }
    }
    
  • typeofinstanceof 类型判断

    function testUnion1(union: string | number) {
      if (typeof union === 'string') {
        // 推导为 string 类型
        union.length
      } else {
        // 推导为 number 类型
        union.toFixed(1)
      }
    }
    
    function testUnion2(union: string | string[]) {
      if (union instanceof Array) {
        // 推导为 string[] 类型
        union.push('1')
      } else {
        // 推导为 string 类型
        union.charAt(1)
      }
    }
    

& 交叉类型(且)

提示

  • 概念:指定某个变量为多个类型之和。
let cross: { a: string; b: number } & { a: string; c: string[] } = {
  // 同时拥有 a、b、c 属性
  a: '1',
  b: 2,
  c: ['1']
}

4.Function 函数

函数类型

提示

  • 概念:函数类型的重点在于定义输入和输出的类型。
// 1.函数声明
// a: number = 10 默认参数
// b?: number 可选参数
const fn1 = (a: number = 10, b?: number): number => {
  return a + (b ? b : 0)
}

// 2.入参收敛
// ...rest: number[] // 收敛剩余参数到数组rest中
const fn2 = (a: number, ...rest: number[]): void => {}

// 3.接口声明函数
interface IFn3 {
  (a: string, b?: string): string
}
const fn3: IFn3 = (a = '1', b = '2') => {
  return `${a}${b}`
}

函数重载

提示

  • 概念:不同入参可以自动匹配不同的函数类型,同一个函数具有多种参数列表。
// 返回值类型:接口声明
interface INum {
  top: number
  bottom: number
  left: number
  right: number
}
// 重载类型:函数在不同输入状态下的表现
function fn4(all: number): INum
function fn4(top: number, bottom: number): INum
function fn4(top: number, bottom: number, left: number): INum
function fn4(top: number, bottom: number, left: number, right: number): INum
function fn4(a: number, b?: number, c?: number, d?: number) {
  !b && (b = a)
  !c && (c = a)
  !d && (d = a)
  return { top: a, bottom: b, left: c, right: d }
}
// 入参数量不同触发不同的函数类型
fn4(1)
fn4(1, 2)
fn4(1, 2, 3)
fn4(1, 2, 3, 4)

5.Class 类

普通类

提示

  • Class:定义了一切事物的抽象特点。
  • 对象 Object:类的实例。
  • 面向对象 OOP:
    • 封装 Encapsulation:将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象。
    • 继承 Inheritance:子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性。
    • 多态 Polymorphism:由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。
// 一.父类
class Car {
  // 类静态属性
  static type: string

  // 类实例属性
  public a: string = 'a' // 公共属性:可被外部、 内部、子类访问(不使用修饰符默认 public)
  protected b: number = 1 // 保护属性:可被内部、子类访问
  private c: boolean = true // 私有属性:可被内部访问
  public d!: string // 非空属性:默认状态下 ts 要求类实例属性都必须有初始值,可通过!强制告诉编译器该值不为空
  readonly e: string = 'e' // 只读属性:只能读取不能修改

  // 类构造函数
  constructor(d = '父类实例化属性') {
    this.d = d
  }

  public fn1() {}
  protected fn2() {}
  private fn3() {}
}

// 1.访问类的静态属性
Car.type
// 2.外部实例可以访问 public 属性
const car = new Car()
car.fn1()
car.a

// 二.子类
// 子类可以访问 public + protected 属性
class BBA extends Car {
  constructor(d: string) {
    super(d)
    this.d = '子类实例化属性'
  }

  // 子类可以覆写父类方法
  fn1() {
    console.log(this.a, this.b)
  }
}

abstract 抽象类

提示

  • 如果多个类存在相同的属性或方法,可以使用抽象类将其抽象到公共类中。
  • 抽象类不能被实例化,只能被继承;抽象类中可以定义抽象属性和方法,但是不能被实现。
  • interfaceabstract 的区别:
    • interface 主要用于 定义对象 公共的属性和方法。
    • abstract 主要用于 定义类 公共的属性和方法。
// 抽象类:抽象类只能被继承,不能直接实例化
abstract class Test {
  // 定义抽象属性:注意抽象属性是不可以被实现的
  abstract getTest(): string
  // 定义具体属性
  width: number

  getType() {
    return 'test'
  }
}

// 子类需要继承抽象类
class Test1 extends Test {
  // 需要实现抽象类中定义的抽象属性
  getTest() {
    return 'test'
  }
}

implements 实现

提示

  • 概念:解耦类继承的一种机制。OOP 中类只能继承于另一个类,不同类之间共同的特性无法继承,这部分特性可以通过 接口 + implements 实现。
// 1.定义 interface 接口
interface IRadio {
  switchRadio: (radio: boolean) => void
}

// 2.类 implements 实现
class Phone implements IRadio {
  switchRadio(radio: boolean) {}
}
class Tv implements IRadio {
  switchRadio(radio: boolean) {}
}

6.Enum 枚举

普通枚举

提示

  • 概念:一种映射机制,只有枚举值为数字的才能进行反向枚举
/**
 * 1.数字枚举:同名枚举可以合并
 * {
 *   0: "Up",
 *   1: "Down",
 *   2: "Left",
 *   3: "Right",
 *   Up: 0,
 *   Down: 1,
 *   Left: 2,
 *   Right: 3
 * }
 */
enum ENUM1 {
  Up,
  Down,
  Left,
  Right
}
enum ENUM1 {
  Center = 4
}
// 正向枚举
ENUM1.Up // -> 0
ENUM1.Down // -> 1
ENUM1.Left // -> 2
ENUM1.Right // -> 3
ENUM1.Center // -> 4
// 反向枚举
ENUM1[0] // -> 'Up'
ENUM1[1] // -> 'Down'
ENUM1[2] // -> 'Left'
ENUM1[3] // -> 'Right'
ENUM1[4] // -> 'Center'

/**
 * 2.指定初始值枚举
 * {
 *   10: "Up",
 *   11: "Down",
 *   12: "Left",
 *   13: "Right",
 *   Up: 10,
 *   Down: 11,
 *   Left: 12,
 *   Right: 13
 * }
 */
enum ENUM2 {
  Up = 10,
  Down,
  Left,
  Right
}
// 正向枚举
ENUM2.Up // -> 10
ENUM2.Down // -> 11
ENUM2.Left // -> 12
ENUM2.Right // -> 13
// 反向枚举
ENUM2[10] // -> 'Up'
ENUM2[11] // -> 'Down'
ENUM2[12] // -> 'Left'
ENUM2[13] // -> 'Right'

/**
 * 3.字符串枚举
 * {
 *   Up: "a",
 *   Down: "b",
 *   Left: "c",
 *   Right: "d"
 * }
 */
enum ENUM3 {
  Up = 'a',
  Down = 'b',
  Left = 'c',
  Right = 'd'
}
// 正向枚举(无数字值不能反向枚举)
ENUM3.Up // -> 'a'
ENUM3.Down // -> 'b'
ENUM3.Left // -> 'c'
ENUM3.Right // -> 'd'

/**
 * 4.异构枚举
 * {
 *   1: "Up",
 *   2: "Left",
 *   Up: 1,
 *   Down: "a",
 *   Left: 2,
 *   Right: "b"
 * }
 */
enum ENUM4 {
  Up = 1,
  Down = 'a',
  Left = 2,
  Right = 'b'
}
// 正向枚举
ENUM4.Up // -> 1
ENUM4.Down // -> 'a'
ENUM4.Left // -> 2
ENUM4.Right // -> 'b'
// 反向枚举
ENUM4[1] // -> 'Up'
ENUM4[2] // -> 'Left'

常量枚举

提示

  • 概念:常量枚举不生成 js 代码而是在调用处直接编译值,理论上性能更好,但 不能使用反向枚举
const enum ENUM5 {
  Up,
  Down,
  Left,
  Right
}

// 不生成 js 代码,直接编译值到调用处
ENUM5.Up // -> 0

7.Generics 泛型

普通泛型

提示

  • 概念:定义函数、接口、类时不定义具体类型,使用时在动态决定类型的一种容器机制。
  • 函数泛型

    // 1.单一泛型
    function gen1<T>(val: T): T {
      return val
    }
    function gen2<T>(arr: T[]): number {
      return arr.length
    }
    
    // 2.多个泛型
    function gen3<T, U>(tuple: [T, U]): number {
      return tuple.length
    }
    
  • 接口泛型

    interface IGen1<T> {
      (val: T): T
    }
    
    // 作用于函数
    const igen1: IGen1<number> = val => val
    
  • 类泛型

    class Stack<T> {
      public arr: T[] = []
    
      public push(item: T) {
        this.arr.push(item)
      }
    
      public pop() {
        this.arr.pop()
      }
    }
    

约束泛型

提示

  • 概念:收敛泛型可能传入的类型。
    • extends:约束于指定的类型。
    • keyof:获取 接口/类型定义的 key 集合 的联合类型。
    • is:判断值是否为指定类型。
// 1.extends 约束指定类型:T 只能是 string 或 number
function gen4<T extends string | number>(val: T): T {
  return val
}
gen4(1)
gen4('a')

// 2.keyof 获取值为对象 key 的联合类型:T 只能是 IGen5、U 只能是 IGen5 的 key
interface IGen5 {
  name: string
  age: number
}
function gen5<T extends IGen5, U extends keyof T>(obj: T, key: U): T[U] {
  // U: 'name' | 'age'
  return obj[key]
}
gen5({ name: 'zxy', age: 18 }, 'age')

// 3.is 判断值是否为指定类型,主要是会带有特定的类型守卫功能
function isString(val: unknow): val is string {
  return typeof val === 'string'
}
function genString(val: number | string) {
  if (isString(val)) {
    // is 关键字可以在这里将 val 限定为 string,number 则不行
    console.log(val.length)
  }
}

多类型融合泛型

提示

  • 概念:可以约束泛型为多类型的融合。
interface IGenA {
  name: string
}
interface IGenB {
  age: number
}

// 1.交叉类型:且
function gen6<T extends IGenA & IGenB>(val: T): T {
  return val
}
gen6({ name: 'zxy', age: 18 })

// 2.联合类型:或
function gen7<T extends IGenA | IGenB>(val: T): T {
  return val
}
gen7({ name: 'zxy' })
gen7({ age: 18 })

实例化泛型

提示

  • 概念:指定泛型为某个类的实例。
function genNew<T>(val: { new (): T }): T {
  return new val()
}

// 返回类的实例
genNew(Date)

8.Decorator 装饰器

警告

截止目前 Decorator 装饰器、Metadata 元数据的提案还在不停变化,暂无法保证该语法的稳定性,使用需谨慎。

TypeScript 使用装饰器需要开启对应配置项:

  • tsconfig.json

    {
      "compilerOptions": {
        "experimentalDecorators": true, // 开启装饰器语法
        "emitDecoratorMetadata": true // 开启装饰器元信息
      }
    }
    

类装饰器

提示

  • 概念:装饰器本质是一个函数,通过 @ 符号来使用,通过在原有代码外层包装了一层处理逻辑实现功能,不影响类本身的方法和属性。
  • 执行时机:类定义立即执行、类实例化不会再次执行。
  • 执行顺序:不同类装饰器从上到下、同类装饰器从下到上。
/**
 * 1.类装饰器参数
 * - constructor:类的构造函数
 */
function classDecorator(constructor: any): void {
  // 为装饰的类增加实例方法
  constructor.prototype.getName = () => {}
}

// 修饰类
@classDecorator
class Test {}

/**
 * 2.类装饰器执行时机
 */
@classDecorator
class Test {} // 类定义立即执行
const test = new Test() // 类实例化不会再次执行

/**
 * 3.类装饰器执行顺序
 */
@classDecorator2
@classDecorator1 // classDecorator1 先于 classDecorator2 执行
class Test1 {} // Test1 先于 Test2 执行

@classDecorator2
@classDecorator1
class Test2 {}

方法装饰器

提示

  • 执行时机:方法定义立即执行、调用方法不会再次执行。
  • 执行顺序:不同方法装饰器从上到下、同方法装饰器从下到上。
/**
 * 1.方法装饰器参数
 * - target:实例方法对应类的 prototype、静态方法对应类的构造函数
 * - key:方法名
 * - descriptor:方法的属性描述符
 *  - value:属性值,这里就是方法本身
 *  - writable:是否可修改
 *  - enumerable:是否可枚举,例如 for in
 *  - configurable:是否可配置,例如 delete
 */
function methodsDecorator(
  target: any,
  key: string,
  descriptor: PropertyDescriptor
): void {
  descriptor.writable = false // 设置修饰方法不能被重写
}

class Test {
  // 修饰实例方法
  @methodsDecorator
  getName() {}

  // 修饰静态方法
  @methodsDecorator
  static getAge() {}
}

const test = new Test('zhang')
test.getName = () => {} // 设置过属性描述符 descriptor.writable 后外部无法修改实例方法

/**
 * 2.方法装饰器执行时机
 */
class Test {
  @methodsDecorator
  getName() {} // 方法定义立即执行
}
const test = new Test()
test.getName() // 调用方法不会再次执行

/**
 * 3.方法装饰器执行顺序
 */
class Test {
  @methodsDecorator2
  @methodsDecorator1 // classDecorator1 先于 classDecorator2 执行
  getName1() {} // getName1 先于 getName2 执行

  @methodsDecorator2
  @methodsDecorator1
  getName2() {}
}

属性装饰器

提示

  • 概念:属性装饰器无法访问到类实例上的属性,只能访问到类原型上的属性。
  • 执行时机:属性定义立即执行、访问属性不会再次执行。
  • 执行顺序:不同属性装饰器从上到下、同属性装饰器从下到上。
/**
 * 1.属性装饰器参数
 * - target:类的 prototype
 * - key:属性名
 */
function propertyDecorator(target: any, key: string): any | void {
  /**
   * 修改原型属性
   * 注意:这种方式不能修改 Test 实例的 name,因为 target[key] 访问的是 Test.prototype[key],两者不一样
   */
  target[key] = 'yu'
  /**
   * 属性装饰器可以返回一个 descriptor 属性描述符
   *  - value:属性值
   *  - writable:是否可修改
   *  - enumerable:是否可枚举,例如 for in
   *  - configurable:是否可配置,例如 delete
   */
  const descriptor: PropertyDescriptor = {
    writable: false
  }
  return descriptor
}

class Test {
  // 修饰实例属性
  @propertyDecorator
  public name: string = 'zhang'
}

const test = new Test()
test.name = 'xin' // 设置过属性描述符 descriptor.writable 后外部无法修改实例属性:Cannot assign to read only property 'name' of object '#<Test>'

/**
 * 2.属性装饰器执行时机
 */
class Test {
  @propertyDecorator
  public name: string = 'zhang' // 属性定义立即执行
}
const test = new Test()
test.name // 访问属性不会再次执行

/**
 * 3.属性装饰器执行顺序
 */
class Test {
  @propertyDecorator2
  @propertyDecorator1 // propertyDecorator1 先于 propertyDecorator2 执行
  public name: string = 'zhang' // name 先于 age 执行

  @propertyDecorator2
  @propertyDecorator1
  private age: number = 18
}

访问器装饰器

提示

  • 概念:TypeScript 规定 getset 上只能使用一个装饰器,如果同时使用会报错。
  • 执行时机:访问器定义立即执行、调用访问器不会再次执行。
  • 执行顺序:不同访问器装饰器从上到下。
/**
 * 1.访问器装饰器参数
 * - target:类的 prototype
 * - key:属性名
 * - descriptor:属性描述符
 *  - get:访问器 get 函数
 *  - set:访问器 set 函数
 *  - enumerable:是否可枚举,例如 for in
 *  - configurable:是否可配置,例如 delete
 */
function visitDecorator(
  target: any,
  key: string,
  descriptor: PropertyDescriptor
) {
  // 注意 writable 最好用于 getter,在 setter 设置会报错
  descriptor.writable = false
}

class Test {
  private _name: string

  constructor(name: string) {
    this._name = name
  }

  // 修饰访问器
  @visitDecorator
  get name() {
    return this._name
  }

  set name(value: string) {
    this._name = value
  }
}

const test = new Test('zhang')
test.name = 'xin' // 设置过属性描述符 descriptor.writable 后外部无法修改实例属性

/**
 * 2.访问器装饰器执行时机
 */
class Test {
  private _name: string

  constructor(name: string) {
    this._name = name
  }

  @visitDecorator // 访问器定义立即执行
  get name() {
    return this._name
  }

  set name(value: string) {
    this._name = value
  }
}
const test = new Test()
test.name // 调用访问器不会再次执行

函数参数装饰器

提示

  • 概念:TypeScript 规定一个函数参数只能使用一个函数参数装饰器。
  • 执行时机:函数参数定义立即执行、调用函数不会再次执行。
  • 执行顺序:不同函数参数装饰器从上到下。
/**
 * 1.函数参数装饰器参数
 * - target:函数所属类的 prototype
 * - key:函数名
 * - paramIndex:参数所属位置,例如 0、1
 */
function paramsDecorator(target: any, key: string, paramIndex: number): any {}

class Test {
  getInfo(
    // 修饰函数参数,对应 paramIndex 0
    @paramsDecorator name: string,
    // 修饰函数参数,对应 paramIndex 1
    @paramsDecorator age: number
  ) {}
}

reflect-metadata 元数据

提示

  • 概念:在类或类属性上存储一些元数据(额外的数据),方便后续利用反射获取数据。TypeScript 中经常与 Decorators 装饰器 联合使用,可访问 reflect-metadataopen in new window 查看更多。
  • 作用:为 Reflect 扩展元数据相关的静态方法。

安装

npm i reflect-metadata -S

常用 API

import 'reflect-metadata'

// 1-1.增、改:在当前类或类属性上定义元数据
Reflect.defineMetadata(metadataKey, metadataValue, target)
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey)
// 1-2.增、改:在当前类、方法、属性上定义元数据(装饰器方式)
@Reflect.metadata(metadataKey, metadataValue)
class Test {
  @Reflect.metadata(metadataKey, metadataValue)
  getName() {}
}

// 2-1.查:获取当前类或类属性(含原型)的元数据 value
const result = Reflect.getMetadata(metadataKey, target)
const result = Reflect.getMetadata(metadataKey, target, propertyKey)
// 2-2.查:获取当前类或类属性本身(不含原型)的元数据 value
const result = Reflect.getOwnMetadata(metadataKey, target)
const result = Reflect.getOwnMetadata(metadataKey, target, propertyKey)
// 2-3.查:获取当前类或类属性(含原型)的元数据 key
const result = Reflect.getMetadataKeys(target)
const result = Reflect.getMetadataKeys(target, propertyKey)
// 2-4.查:获取当前类或类属性本身(不含原型)的元数据 key
const result = Reflect.getOwnMetadataKeys(target)
const result = Reflect.getOwnMetadataKeys(target, propertyKey)

// 3.删:删除当前类或类属性指定 key 的元数据
const result = Reflect.deleteMetadata(metadataKey, target)
const result = Reflect.deleteMetadata(metadataKey, target, propertyKey)

// 4-1.判断:当前类或类属性(含原型)是否存在指定 key 的元数据
const result = Reflect.hasMetadata(metadataKey, target)
const result = Reflect.hasMetadata(metadataKey, target, propertyKey)
// 4-2.判断:当前类或类属性本身(不含原型)是否存在指定 key 的元数据
const result = Reflect.hasOwnMetadata(metadataKey, target)
const result = Reflect.hasOwnMetadata(metadataKey, target, propertyKey)

基础使用

import 'reflect-metadata'

/**
 * 1.对象
 */
const user = { name: 'zhang' }
// 定义元数据:key:'111'、value:'test1'、target:user
Reflect.defineMetadata('111', 'test1', user)
// 获取元数据:key:'111'、target:user
Reflect.getMetadata('111', user) // -> 'test1'
// 普通打印
console.log(user) // -> { name: 'zhang' }

/**
 * 2.类
 */
// 定义元数据:key:'222'、value:'test2'
@Reflect.metadata('222', 'test2')
class User {
  name = 'zhang'
}
// 获取元数据:key:'222'、target:User
Reflect.getMetadata('222', User) // -> 'test2'

/**
 * 3.类属性
 */
class User {
  // 定义元数据:key:'333'、value:'test3'
  @Reflect.metadata('333', 'test3')
  name = 'zhang'
}
// 获取元数据:key:'333'、target:User.prototype、targetProperty:'name'
Reflect.getMetadata('333', User.prototype, 'name') // -> 'test3'

/**
 * 4.类方法
 */
class User {
  // 定义元数据:key:'444'、value:'test4'
  @Reflect.metadata('444', 'test4')
  getName() {}
}
// 获取元数据:key:'444'、target:User.prototype、targetProperty:'getName'
Reflect.getMetadata('data', User.prototype, 'getName') // -> 'test4'

结合 Decorator 实现后端接口路由绑定功能

import 'reflect-metadata'
import { BodyRequest, RequestHandler, Response, Router } from 'express'

// 实例化 express 路由
const router = new Router()

/**
 * 1.方法装饰器
 * - 作用:通过工厂模式定义 http 方法和请求路径
 */
function getRequestDecorator(method: 'get' | 'post') {
  return function (path: string) {
    return function (target: any, key: string, descriptor: PropertyDescriptor) {
      // 将 http 方法和请求路径通过装饰器绑定到类函数的元数据中
      Reflect.defineMetadata('method', method, target, key)
      Reflect.defineMetadata('path', path, target, key)
    }
  }
}
export const Get = getRequestDecorator('get')
export const Post = getRequestDecorator('post')

/**
 * 2.类装饰器
 * - 作用:获取类中所有绑定的请求路径、http方法、类函数名,自动绑定路由
 */
export function Controller(rootPath: string) {
  return function (constructor: any) {
    const prototype = constructor.prototype
    // 遍历类原型上的 key 获取方法名
    for (let key in prototype) {
      // 1.获取方法上绑定的元数据:请求路径
      const path = Reflect.getMetadata('path', prototype, key)
      // 2.获取方法上绑定的元数据:http方法
      const method = Reflect.getMetadata('method', prototype, key)
      // 3.获取方法
      const handler = prototype[key]
      // 4.部署路由:若请求路径、http方法都存在,则自动绑定express路由
      if (path && method) {
        const fullPath = rootPath ? `${rootPath}${path}` : path
        router[method](fullPath, handler)
      }
    }
  }
}

/**
 * 3.控制器绑定类装饰器、方法装饰器
 */
@Controller('/user')
export class UserController {
  @Get('/list')
  findAll(req: BodyRequest, res: Response): void {
    res.json({
      code: 200,
      message: '成功',
      data: [
        { id: 1, name: '小明', desc: '描述1' },
        { id: 2, name: '小凯', desc: '描述2' },
        { id: 3, name: '小孙', desc: '描述3' }
      ]
    })
  }
}

对比

装饰器多重修饰执行时机执行次数执行顺序
类装饰器允许类定义时1不同类:上->下
同类:下->上
方法装饰器允许方法定义时1不同方法:上->下
同方法:下->上
属性装饰器允许属性定义时1不同属性:上->下
同属性:下->上
访问器装饰器不允许访问器定义时1不同访问器:上->下
函数参数装饰器不允许函数参数定义时1不同函数参数:上->下

同一个类不同装饰器执行顺序:

  • 函数参数装饰器
  • 属性装饰器、访问器装饰器、方法装饰器(谁在上谁先执行,类应按照属性-访问器-方法顺序定义)
  • 类装饰器

9.工具类型

系统工具类型

提示

  • 概念:TypeScript 自带的实用工具类型。
interface IPerson {
  name: string
  age?: number
  address?: string
}

// 1.Partial<Type>:转换接口属性为可选来构造一个新类型
type IPartial = Partial<IPerson> // -> { name?:string; age?:number; address?: string }

// 2.Required<Type>:转换接口属性为必填来构造一个新类型
type IRequired = Required<IPerson> // -> { name:string; age:number; address: string }

// 3.Pick<Type, Keys>:挑选接口某几个属性来构造一个新类型
type IPick = Pick<IPerson, 'age' | 'address'> // -> { age?:number; address?: string }

// 4.Omit<Type, Keys>:移除接口某几个属性来构造一个新类型
type IOmit = Omit<IPerson, 'name'> // -> { age?:number; address?: string }

// 5.NonNullable:移除 null、undefined来构造一个新类型
type INonNullable = NonNullable<string | number | undefined | null> // -> string | number

// 6.Extract<UnionType, ExtractedMembers>:通过两个类型的交集来构造一个新类型
type IExtract = Extract<'1' | '2' | '3', '1' | '2' | '4'> // -> '1' | '2'

// 7.Exclude<UnionType, ExcludedMembers>:通过 ExcludedMembers 的补集来构造一个新类型
type IExclude = Exclude<'1' | '2' | '3', '1'> // -> '2' | '3'

自定义工具类型

  • 属性转换:? 可选、-? 必填、readonly 只读、-readonly 移除只读。

    // 1.泛型属性 -> 必填 -?
    type ZxyTool1<T> = { [K in keyof T]-?: T[K] }
    
    // 2.泛型属性 -> 可选 ?
    type ZxyTool2<T> = { [K in keyof T]?: T[K] }
    
    // 3.泛型属性 -> 只读 readonly
    type ZxyTool3<T> = { readonly [K in keyof T]: T[K] }
    
    // 4.泛型属性 -> 移除只读 -readonly
    type ZxyTool4<T> = { -readonly [K in keyof T]: T[K] }
    
    // 5.泛型属性 -> 深度可选
    type ZxyTool5<T> = {
      [K in keyof T]?: T[K] extends object ? ZxyTool5<T[K]> : T[K]
    }
    
    // 使用 泛型属性深度可选
    interface ITest {
      name: string
      readonly age: number
      from?: string
      info: {
        farther: string
        mother: string
      }
    }
    type D = ZxyTool5<ITest>
    
  • 条件判断:类型 extends 条件 ? 类型1 : 类型2

    // 1.函数返回值条件判断
    function tj<T extends boolean>(val: T): T extends true ? string : number
    
    // 2.获取接口中的函数属性名称
    type FunctionName<T> = {
      // 遍历 T 的 key,对 T 的 value T[K] 做判断,属于 Function 的赋 key,其他的赋 never
      [K in keyof T]: T[K] extends Function ? K : never
    }[keyof T] // 使用 [keyof T] 作为 key 依次取出新 interface 的 value,由于 never 不会返回任何类型所以只返回了 'info'
    
    interface IMap {
      name: string
      age: number
      info(msg: string): void
    }
    type R = FunctionName<IMap> // -> 'info'
    
  • 推导:类型<infer 推导类型结果>

    // 1.推导数组中元素类型
    type ElementOf<T> = T extends Array<infer E> ? E : never
    
    type IMapE = ElementOf<[string, number]> // -> string | number
    

10.d.ts 声明文件

提示

/**
 * 定义全局关键字:declare
 * 变量关键字:var、const、let
 * 函数关键字:function
 * 对象关键字:namespace
 */

// 直接使用会报错
TestFn('#foo')

// 通过 declare 来全局声明一个函数变量类型,避免 ts 报错
declare function TestFn(val: string): void

// 1.定义全局变量
declare var $: (func: () => void) => void

// 2.使用全局函数实现重载:函数重载,一个函数可以有多种形式,定义一个全局函数的多个声明
interface JqueryInstance {
  html: (html: string) => JqueryInstance
}
declare function $(func: () => void): void
declare function $(selector: string): JqueryInstance

// 3.使用 interface 实现函数重载
interface JQuery {
  (func: () => void): void // 本身是函数,参数也是函数
  (sel: string): JqueryInstance // 本身是函数,参数是字符串
}
interface JqueryInstance {
  html: (html: string) => JqueryInstance
}
declare var $: JQuery

// 4.使用 namespace 实现函数重载
// 对对象进行类型定义、对类进行类型定义、namespace 的嵌套
interface JqueryInstance {
  html: (html: string) => JqueryInstance
}
declare function $(func: () => void): void
declare function $(sel: string): JqueryInstance
// $ 定义为命名空间:这里 namespace 就是充当一个属性定义的功能
declare namespace $ {
  // fn 定义为命名空间
  namespace fn {
    // init 定义为 class,可以执行构造函数
    class init {}
  }
}
此文档贡献者: 嗷大张