TypeScript 笔记

1.基础类型

1.1 安装

  1. yarn global add typescript
  2. 编译 .ts 文件:tsc xxx.ts
  3. 运行 .js 文件:node xxx.js

1.2 字符串、数字、布尔

字符串类型

let str:string = 'TS'
let muban:string = `web ${str}`
console.log(muban) // web TS
1
2
3

数字类型

// 基本
let num:number = 123

// 进阶
let notANumber:number = NaN // 非数字
let infinityNumber:number = Infinity // 无穷大
let decimal:number = 6 // 十进制
let octal:number = 0o744 // 八进制
let hex:number = 0xf00d // 十六进制
let binary:number = 0b1010 // 二进制
1
2
3
4
5
6
7
8
9
10

布尔值

// 两个基本的用法
let b:boolean = false
let c:boolean = Boolean(true)
console.log(b, c) // false true

// new 出来的是一个对象
let d:Boolean = new Boolean(false)
console.log(d.valueOf()) // false
1
2
3
4
5
6
7
8

1.3 void、undefined、null

void

// void 的值包括 undefined 和 null
// null 和 undefined 也可以单独声明
let u:void = undefined
let n:void = null
function fnVoid ():void {
  console.log(123)
}
fnVoid()
1
2
3
4
5
6
7
8

单独声明 undefined、null 和声明 void 的区别

void:报错

let v:void = undefined
let str:string = '123'
str = v // 不能将类型“void”分配给类型“string”
1
2
3

单独声明 undefined、null:不报错

let v:undefined = undefined // v:null 也不报错
let str:string = '123'
str = v
1
2
3

2.任意类型

Any 类型和 unknown 类型都是顶级类型

2.1 直接运行 TS

  1. 安装:yarn add @types/node
  2. 安装:yarn global add ts-node
  3. 直接运行 ts:ts-node xxx.ts

2.2 any 类型和 unknown 类型对比

unknown 类型比 any 类型更安全

unknown 类型不能去调用属性和方法

let anys:any = { a: 123 }
console.log(anys.a) // 不报错

let unknow:unknown = { a: 123 }
console.log(unknow.a) // 报错

let unknoww:unknown = { a: ():number => 123 }
console.log(unknoww.a) // 报错
1
2
3
4
5
6
7
8

unknown 赋值的话只能被赋值

let str2:unknown = '我是袁珂'
let str3:string = '我不是袁珂'
// str3 = str2 // 报错
// unknown 赋值的话只能被赋值
str2 = str3 // 不报错
1
2
3
4
5

3.接口和对象类型

3.1 接口的一些特性

重名 interface 会合并

interface A {
  name: string
}
// 重名会合并
interface A {
  age: number
}
let obj:A = {
  name: '嗷嗷',
  age: 18
}
console.log(obj)
1
2
3
4
5
6
7
8
9
10
11
12

可选属性

// 可选式操作符 ?
interface Person {
  name: string,
  age?: number
}
let p:Person = {
  name: 'zhangsan'
}
1
2
3
4
5
6
7
8

任意属性 [propName: string]

一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

interface Person {
  name: string,
  age?: number,
  [propName: string]: unknown
}
let p:Person = {
  name: 'yuanke',
  address: '西湖',
  salary: 30000
}
1
2
3
4
5
6
7
8
9
10

只读属性 readonly

interface Person {
  readonly name: string,
  age?: number,
  [propName: string]: string | number
}
let p:Person = {
  name: 'yuanke',
  address: '西湖',
  salary: 30000
}
p.name = 'haha' // 报错,它是只读属性
1
2
3
4
5
6
7
8
9
10
11

接口中使用函数

interface Person {
  readonly name: string,
  age?: number,
  // 接口里的函数声明
  cb():number,
  [propName: string]: unknown
}
let p:Person = {
  name: 'yuanke',
  address: '西湖',
  salary: 30000,
  cb: ():number => {
    return 123
  }
}
console.log(p.cb())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

3.2 接口继承

interface A {
  name: string
}
interface B extends A {
  age: number
}
let p:B = {
  name: 'yuanke',
  age: 18
}
1
2
3
4
5
6
7
8
9
10

4.数组类型

4.1 普通数组

一维数组

// 数字类型的数组
let arr:number[] = [1, 2, 3, 88]
// 字符串类型的数组
let arr2:string[] = ['a', 'b']
// 任意类型的数组
let arr3:any[] = ['a', 12, true]
1
2
3
4
5
6

多维数组

// 二维数组
let arr2:number[][] = [[1, 3], [6, 9], [5, 3]]
1
2

4.2 数组的泛型写法

一维数组

let arr:Array<number> = [1, 23, 66]
let arr2:Array<string> = ['ha', 'he', 'ah']
let arr3:Array<boolean> = [true, false]
1
2
3

多维数组

// 二维数组,其中 | 为联合类型的符号
let arr:Array<Array<number | string>> = [[1, 3], [2, 4, 'haha']]
1
2

4.3 类数组

类数组的类为 IArguments

类数组的类型 - IArguments

function Arr (...args: number[]):void {
  console.log(arguments) // { '0': 4, '1': 5, '2': 6 }
  let likeArr:IArguments = arguments
  console.log(likeArr) // { '0': 4, '1': 5, '2': 6 }
}
Arr(4, 5, 6)
1
2
3
4
5
6

类数组的定义

interface ArrLike {
  [index:number]:number
}
let arr:ArrLike = {
  0: 3,
  2: 6
}
console.log(arr) // { '0': 3, '2': 6 }

// 输入普通数组也灵
let arr2:ArrLike = [1, 3, 5]
console.log(arr2)
1
2
3
4
5
6
7
8
9
10
11
12

5.函数类型

5.1 函数的一些特性

可选式操作符

const fn = function (name: string, age?: number): string {
  return name + age
}
let a = fn('张三')
console.log(a) // 张三undefined
1
2
3
4
5

使用接口规范上面的代码

interface User {
  name: string,
  age?: number
}
const fn = function (user:User): User {
  return user
}
let a = fn({
  name: 'zhangsan',
  age: 18
})
console.log(a) // { name: 'zhangsan', age: 18 }
1
2
3
4
5
6
7
8
9
10
11
12

5.2 函数重载

函数重载是方法名字相同,而参数不同,返回类型可以相同也可以不同。如果参数类型不同,则执行函数参数类型应设为 any,参数数量不同可以将不同的参数设置为可选

// 前两个函数是重载函数,最后一个函数是执行函数
function fn(params: number): void
function fn(params: string, params2: number): void
function fn(params: any, params2?: any): void {
  console.log(params)
  console.log(params2)
}
fn(1) // 1 undefined
fn('1', 2) // 1 2
1
2
3
4
5
6
7
8
9

6.联合类型 | 类型断言 | 交叉类型

6.1 联合类型

其实就是个 | 而已

// 1 -> true、0 -> false、true -> true、false -> false
let fn = function (type: number | boolean):boolean {
  return !!type
}
let result = fn(1)
console.log(result)
1
2
3
4
5
6

6.2 交叉类型

其实就是个 & 而已

interface People {
  name: string,
  age: number
}
interface Man {
  sex: number
}
// & 类似接口的 extends 的作用
const yuanke = (man: People & Man): void => {
  console.log(man) // { name: '今天好', age: 108, sex: 1 }
}
yuanke({
  name: '今天好',
  age: 108,
  sex: 1
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

6.3 类型断言

不能滥用类型断言,否则在运行时可能有错误

基本使用

let fn = function (num: number | string): void {
  // 如果不写 as 就会报错
  console.log((num as string).length)
}
fn(12345) // undefined
fn('12345') // 5
1
2
3
4
5
6

ts 中使用 window

(window as any).abc = 123
1

另一种断言方法

interface A {
  run: string
}
interface B {
  build: string
}
let fn = (type: A | B): void => {
  console.log((<A>type).run)
  console.log((type as A).run)
}
1
2
3
4
5
6
7
8
9
10

类型断言只是欺骗编译器,运行结果仍为原来的运行结果

const fn = (type: any): boolean => {
  return type as boolean
}
// 这里结果仍为 1,而不是断言的布尔值
console.log(fn(1)) // 1
1
2
3
4
5

7.内置对象

7.1 常见内置对象

// 正则表达式
const regexp: RegExp = /\w\d\s/

// 日期
const date: Date = new Date()
console.log(date) // 2022-05-05T03:44:57.041Z

// Error
const error: Error = new Error('错误')
1
2
3
4
5
6
7
8
9

7.2 DOM 和 BOM 的内置对象

TypeScript/src/lib at main · microsoft/TypeScript (github.com)open in new window

映射表

let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
// 读取 div 这种需要类型断言 或者加个判断应为读不到返回 null
let div:HTMLElement = document.querySelector('div') as HTMLDivElement
document.addEventListener('click', function (e: MouseEvent) {
    
});
// dom 元素的映射表
interface HTMLElementTagNameMap {
    "a": HTMLAnchorElement;
    "abbr": HTMLElement;
    "address": HTMLElement;
    "applet": HTMLAppletElement;
    "area": HTMLAreaElement;
    "article": HTMLElement;
    "aside": HTMLElement;
    "audio": HTMLAudioElement;
    "b": HTMLElement;
    "base": HTMLBaseElement;
    "bdi": HTMLElement;
    "bdo": HTMLElement;
    "blockquote": HTMLQuoteElement;
    "body": HTMLBodyElement;
    "br": HTMLBRElement;
    "button": HTMLButtonElement;
    "canvas": HTMLCanvasElement;
    "caption": HTMLTableCaptionElement;
    "cite": HTMLElement;
    "code": HTMLElement;
    "col": HTMLTableColElement;
    "colgroup": HTMLTableColElement;
    "data": HTMLDataElement;
    "datalist": HTMLDataListElement;
    "dd": HTMLElement;
    "del": HTMLModElement;
    "details": HTMLDetailsElement;
    "dfn": HTMLElement;
    "dialog": HTMLDialogElement;
    "dir": HTMLDirectoryElement;
    "div": HTMLDivElement;
    "dl": HTMLDListElement;
    "dt": HTMLElement;
    "em": HTMLElement;
    "embed": HTMLEmbedElement;
    "fieldset": HTMLFieldSetElement;
    "figcaption": HTMLElement;
    "figure": HTMLElement;
    "font": HTMLFontElement;
    "footer": HTMLElement;
    "form": HTMLFormElement;
    "frame": HTMLFrameElement;
    "frameset": HTMLFrameSetElement;
    "h1": HTMLHeadingElement;
    "h2": HTMLHeadingElement;
    "h3": HTMLHeadingElement;
    "h4": HTMLHeadingElement;
    "h5": HTMLHeadingElement;
    "h6": HTMLHeadingElement;
    "head": HTMLHeadElement;
    "header": HTMLElement;
    "hgroup": HTMLElement;
    "hr": HTMLHRElement;
    "html": HTMLHtmlElement;
    "i": HTMLElement;
    "iframe": HTMLIFrameElement;
    "img": HTMLImageElement;
    "input": HTMLInputElement;
    "ins": HTMLModElement;
    "kbd": HTMLElement;
    "label": HTMLLabelElement;
    "legend": HTMLLegendElement;
    "li": HTMLLIElement;
    "link": HTMLLinkElement;
    "main": HTMLElement;
    "map": HTMLMapElement;
    "mark": HTMLElement;
    "marquee": HTMLMarqueeElement;
    "menu": HTMLMenuElement;
    "meta": HTMLMetaElement;
    "meter": HTMLMeterElement;
    "nav": HTMLElement;
    "noscript": HTMLElement;
    "object": HTMLObjectElement;
    "ol": HTMLOListElement;
    "optgroup": HTMLOptGroupElement;
    "option": HTMLOptionElement;
    "output": HTMLOutputElement;
    "p": HTMLParagraphElement;
    "param": HTMLParamElement;
    "picture": HTMLPictureElement;
    "pre": HTMLPreElement;
    "progress": HTMLProgressElement;
    "q": HTMLQuoteElement;
    "rp": HTMLElement;
    "rt": HTMLElement;
    "ruby": HTMLElement;
    "s": HTMLElement;
    "samp": HTMLElement;
    "script": HTMLScriptElement;
    "section": HTMLElement;
    "select": HTMLSelectElement;
    "slot": HTMLSlotElement;
    "small": HTMLElement;
    "source": HTMLSourceElement;
    "span": HTMLSpanElement;
    "strong": HTMLElement;
    "style": HTMLStyleElement;
    "sub": HTMLElement;
    "summary": HTMLElement;
    "sup": HTMLElement;
    "table": HTMLTableElement;
    "tbody": HTMLTableSectionElement;
    "td": HTMLTableDataCellElement;
    "template": HTMLTemplateElement;
    "textarea": HTMLTextAreaElement;
    "tfoot": HTMLTableSectionElement;
    "th": HTMLTableHeaderCellElement;
    "thead": HTMLTableSectionElement;
    "time": HTMLTimeElement;
    "title": HTMLTitleElement;
    "tr": HTMLTableRowElement;
    "track": HTMLTrackElement;
    "u": HTMLElement;
    "ul": HTMLUListElement;
    "var": HTMLElement;
    "video": HTMLVideoElement;
    "wbr": HTMLElement;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

示例代码

const list:NodeList = document.querySelectorAll('#list li')
const body:HTMLElement = document.body
const div:HTMLDivElement = document.querySelector('div')

document.addEventListener('click', (e: MouseEvent) => {
  console.log(e)
})
1
2
3
4
5
6
7

7.3 Promise

function promise(): Promise<number> {
  // 使用泛型使返回值设为数字
  return new Promise<number>((resolve, reject) => {
    resolve(1)
  })
}

promise().then(res => {
  console.log(res) // 1
})
1
2
3
4
5
6
7
8
9
10

8.Class

8.1 public、private、protected

public 内部外部都能访问,private 只能在内部访问,protected 在内部和子类中访问

private、protected 用法

class Person {
  protected name: string
  private age: number
  sub: boolean
  constructor(name: string, age: number, sub: boolean) {
    this.name = name
    this.age = age
    this.sub = sub
  }
}

class Man extends Person {
  constructor() {
    super('yuanke3', 11, true)
    console.log(this.name) // 不报错,可以访问 protected 属性
    console.log(this.age) // 报错,不可访问 private 属性
  }
}

let p = new Person('yuanke', 22, false)
console.log(p.age) // 报错,不可访问 private 属性
console.log(p.name) // 报错,不可访问 protected 属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

8.2 静态属性、静态方法

示例代码

class Person {
  static aaa: string = '123455'
  static run() {
    return '789'
  }
}

console.log(Person.aaa) // 123455
console.log(Person.run()) // 789
1
2
3
4
5
6
7
8
9

静态方法访问不到非静态属性,constructor 里调用不了静态方法

class Person {
  public name: string
  public age: number
  public sub: boolean
  static aaa: string = '123455'
  constructor(name: string, age: number, sub: boolean) {
    this.name = name
    this.age = age
    this.sub = sub
    // this.run() // 报错
  }
  static run() {
    // console.log(this.age) // 报错,静态方法访问不了非静态属性
    console.log(this.aaa) // 123455,静态方法可以访问静态属性
    return '789'
  }
  static dev() {
    this.run() // 静态方法可以调用静态方法
  }
}

console.log(Person.aaa) // 123455
console.log(Person.run()) // 789
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

8.3 使用接口约束类

implement 约束类

interface Person {
  run(type: boolean): boolean
}

class Man implements Person {
  run(type: boolean): boolean {
    return type
  }
}
1
2
3
4
5
6
7
8
9

extends 类继承和 implement 约束类相结合

interface Person {
  run(type: boolean): boolean
}

interface H {
  set(): void
}

class A {
  params: string
  constructor(params: string) {
    this.params = params
  }
}

class Man extends A implements Person, H {
  run(type: boolean): boolean {
    return type
  }
  set(): void {
    console.log(this.params)
    console.log('hello')
  }
}

let man = new Man('google')
man.set() // hello
console.log(man.run(false)) // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

8.4 抽象类

抽象类的函数不能写具体实现,且抽象类不能被实例化

abstract class A {
  name: string
  constructor(name: string) {
    this.name = name
  }
  // 下面这个不是抽象方法
  setName(name: string) {
    this.name = name
  }
  abstract getName(): string
}

class B extends A {
  constructor() {
    super('yuanke')
  }
  getName(): string {
    return this.name
  }
}

let b = new B()
b.setName('yuanke2')
console.log(b.getName())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

9.元组类型

如果需要一个固定大小的不同类型值的集合,就需要元组

9.1 基本用法

基本使用

// 不能写成 ['yuanke', 22, 1],只能为固定大小
let arr: [string, number] = ['yuanke', 22]
// 报错,只能包括上面规定的字符串和数字
arr.push(true)
1
2
3
4

数组与元组的写法差异

// 数组
let arr: number[] = [6, 2]
let arr1: unknown[] = ['haha', 12]
let arr2: Array<number> = [1, 2]

// 元组
let tuple: [number, string] = [1, 'good']
1
2
3
4
5
6
7

10.枚举类型

10.1 基本用法 & 增长枚举

枚举基本使用

// low 的写法
let obj = {
  red: 0,
  green: 1,
  blue: 2
}

// 枚举的写法
enum Color {
  red,
  green,
  blue
}
console.log(Color.red) // 0
console.log(Color.blue) // 2
console.log(Color.green) // 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

增长枚举

enum Color {
  // 规定了起始的值,后面的都会自动加一
  red = 1,
  green,
  blue
}
console.log(Color.red) // 1
console.log(Color.blue) // 3
console.log(Color.green) // 2
1
2
3
4
5
6
7
8
9

10.2 字符串枚举 & 异构枚举

字符串枚举

不会自增了,且每个枚举成员必须具有初始化表达式

enum Color {
  red = 'red',
  green = 'green',
  blue = 'blue'
}
console.log(Color.red) // red
console.log(Color.blue) // blue
console.log(Color.green) // green
1
2
3
4
5
6
7
8

异构枚举:基本不会使用

枚举可以混合字符串和数字成员

enum Color {
  yes = 1,
  no = 'no'
}
console.log(Color.yes) // 1
console.log(Color.no) // no
1
2
3
4
5
6

10.3 接口枚举 & const 枚举

接口枚举

enum Color {
  no = 'no',
  yes = 1
}
interface A {
  red: Color.yes
}
let obj: A = {
  // red: Color.yes // 通过
  red: 1 // 通过
}
1
2
3
4
5
6
7
8
9
10
11

const 枚举

const 声明的枚举会被编译为常量,普通声明的枚举编译完后是个对象

// app.ts 且使用 const -> 编译前
const enum Types {
  success,
  fail
}
let code: number = 0
if (code === Types.success) {
}

// app.js -> 编译后
var code = 0;
if (code === 0 /* success */) {
}

// app.ts 未使用 const -> 编译后
var Types;
(function (Types) {
    Types[Types["success"] = 0] = "success";
    Types[Types["fail"] = 1] = "fail";
})(Types || (Types = {}));
var code = 0;
if (code === Types.success) {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

10.4 反向映射

包括了正向映射 key -> value 和 反向映射 value -> key

enum Types {
  success = 456
}

// Types.success -> 456
let success: number = Types.success
console.log(success) // 0

// Types[456] -> success
let key = Types[success]
console.log(`value---${success}`, `key---${key}`) // value---456 key---success
1
2
3
4
5
6
7
8
9
10
11

11.类型推论 & 类型别名

11.1 类型推论

let str = 'yuanke' // 编译器自动识别 str 为 string 类型
1

11.2 类型别名 - type 实现

基本类型别名

// 类型别名
type s = string | number
let str: s = 'yuanke'
let num: s = 12
1
2
3
4

函数别名

type cb = () => string
const fn: cb = () => 'yuanke'
1
2

字面量别名

type T = 'off' | 'on'
let str: T = 'off'
let str1: T = 'on'
1
2
3

12.never 类型

never 只能作为赋值方,而不能作为被赋值方

12.1 基本使用

类型推论成 never 类型

type bbb = string & number // bbb 推断为 never 类型
1

函数中两个常见定义 never 的例子

function error(message: string): never {
  throw new Error(message)
}

function loop(): never {
  while (true) {
  }
}
1
2
3
4
5
6
7
8

经典例子 - switch

interface A {
  type: '保安'
}

interface B {
  type: '草莓'
}

interface C {
  type: '菜菜'
}

type All = A | B | C

function type(val: All) {
  switch (val.type) {
    case '保安':
      break
    case '草莓':
      break
    default:
      // 在编译过程中就能发现类型 C 的逻辑错误
      const check: never = val 
      break
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

13.Symbol 类型

13.1 基本使用

let s:symbol = Symbol(123)
let num:symbol = Symbol(123)

let obj = {
  [num]: 'value',
  [s]: '草莓',
  name: 'yuanke',
  sex: '男'
}

// for (let key in obj) {
//   console.log(key)
// }

// console.log(Object.keys(obj))

// console.log(Object.getOwnPropertyNames(obj))

// console.log(JSON.stringify(obj))

// 只能读到 Symbol 类型的值
// console.log(Object.getOwnPropertySymbols(obj))

console.log(Reflect.ownKeys(obj)) // [ 'name', 'sex', Symbol(123), Symbol(123) ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

13.2 迭代器 & 生成器

迭代器基本使用

let arr:Array<number> = [4, 5, 6]
let it:Iterator<number> = arr[Symbol.iterator]()
console.log(it.next())
console.log(it.next())
console.log(it.next())
console.log(it.next())
1
2
3
4
5
6

小迭代器的创建

type mapKeys = string | number
let arr: Array<number> = [4, 5, 6]
let set: Set<number> = new Set([1, 2, 3])
let map: Map<mapKeys, mapKeys> = new Map()
map.set('1', 'haha')
map.set('2', 'hehe')
function gen(erg: any) {
  let it: Iterator<any> = erg[Symbol.iterator]()
  let next: any = { done: false }
  while (!next.done) {
    next = it.next()
    if (!next.done) {
      console.log(next)
    }
  }
}

gen(arr)
// { value: 4, done: false }
// { value: 5, done: false }
// { value: 6, done: false }

gen(set)
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }

gen(map)
// { value: [ '1', 'haha' ], done: false }
// { value: [ '2', 'hehe' ], done: false }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

生成器:其实就是 for (xx of xx) {}


14.泛型

14.1 基本使用

语法为函数后面跟一个 <参数名>,参数名可以随便写。当使用这个函数的时候把参数的类型传进来就可以了

使用泛型后,调用函数时的参数可以进行类型推论,且默认为 unknown 类型

image-20220506081854090

两种形式都是对的

function add<T>(a: T, b: T): Array<T> {
  return [a, b]
}

// add<number>(1, 2)
// add<string>('haha', 'hehe')

add(1, 2)
add('haha', 'hehe')
1
2
3
4
5
6
7
8
9

14.2 多个参数 & 泛型约束

多个参数的情况

// function sub<T, U>(a: T, b: U): (T | U)[] {
//   let arr: (T | U)[] = [a, b]
//   return arr
// }
// console.log(sub<number, boolean>(1, false))

function sub<T, U>(a: T, b: U): Array<T | U> {
  let arr: Array<T | U> = [a, b]
  return arr
}
sub<number, boolean>(1, false)
1
2
3
4
5
6
7
8
9
10
11

泛型约束

T 没有继承时默认为 type T = {}。类型 T 要满足拥有 length 属性,需要继承具有 length 属性的类型

// function getLength<T extends { length: number }>(arg: T) {
//   return arg.length
// }

interface Len {
  length: number
}
function getLength<T extends Len>(arg: T) {
  return arg.length
}

console.log(getLength([2, 3])) // 2
console.log(getLength('hahaha')) // 6
// 泛型传递一个数字类型,给该数字强加一个 length 后,return arg.length 会报错,因为数字类型压根没有 length 属性的实现
// getLength(12) // 报错,数字类型没有 length 属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

15.泛型约束 & 泛型类

15.1 泛型约束 - keyof

案例引入

function prop<T>(obj, key) {
  return obj[key]
}
let o = { a: 1, b: 2, c: 3 }
prop(o, 'a')
prop(o, 'd') // 这里不会报错!故此引入泛型约束概念
1
2
3
4
5
6

基本使用

keyof 是用来获取 T 类型的所有键,它的返回类型是联合类型

// K 没有继承默认为 type K = {},继承后变成 type K = { a: string, b: string, c: string },即约束了 key 只拥有这三个属性
function prop<T, K extends keyof T>(obj: T, key: K) {
  return obj[key]
}
let o = { a: 1, b: 2, c: 3 }
prop(o, 'a')
prop(o, 'd') // 报错了!
1
2
3
4
5
6
7

15.2 泛型类

函数中的泛型的声明是在函数名之后,而泛型在类中的声明也是在类名之后

class Sub<T> {
  attr: T[] = []
  add(a: T): T[] {
    return [a]
  }
}
let s = new Sub<number>()
s.attr = [1, 2, 3, 4, 5]
let str = new Sub<string>()
str.attr = ['1', 'a', 'k']
console.log(s, str) // Sub { attr: [ 1, 2, 3, 4, 5 ] } Sub { attr: [ '1', 'a', 'k' ] }
1
2
3
4
5
6
7
8
9
10
11

16.tsconfig.json

16.1 配置详解

"compilerOptions": {
  "incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
  "tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
  "diagnostics": true, // 打印诊断信息 
  "target": "ES5", // 目标语言的版本
  "module": "CommonJS", // 生成代码的模板标准
  "outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
  "lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
  "allowJS": true, // 允许编译器编译JS,JSX文件
  "checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
  "outDir": "./dist", // 指定输出目录
  "rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
  "declaration": true, // 生成声明文件,开启后会自动生成声明文件
  "declarationDir": "./file", // 指定生成声明文件存放目录
  "emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
  "sourceMap": true, // 生成目标文件的sourceMap文件
  "inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
  "declarationMap": true, // 为声明文件生成sourceMap
  "typeRoots": [], // 声明文件目录,默认时node_modules/@types
  "types": [], // 加载的声明文件包
  "removeComments":true, // 删除注释 
  "noEmit": true, // 不输出文件,即编译后不会生成任何js文件
  "noEmitOnError": true, // 发送错误时不输出任何文件
  "noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
  "importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
  "downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
  "strict": true, // 开启所有严格的类型检查
  "alwaysStrict": true, // 在代码中注入'use strict'
  "noImplicitAny": true, // 不允许隐式的any类型
  "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
  "strictFunctionTypes": true, // 不允许函数参数双向协变
  "strictPropertyInitialization": true, // 类的实例属性必须初始化
  "strictBindCallApply": true, // 严格的bind/call/apply检查
  "noImplicitThis": true, // 不允许this有隐式的any类型
  "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
  "noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
  "noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
  "noImplicitReturns": true, //每个分支都会有返回值
  "esModuleInterop": true, // 允许export=导出,由import from 导入
  "allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
  "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
  "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
  "paths": { // 路径映射,相对于baseUrl
    // 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
    "jquery": ["node_modules/jquery/dist/jquery.min.js"]
  },
  "rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
  "listEmittedFiles": true, // 打印输出文件
  "listFiles": true// 打印编译的文件(包括引用的声明文件)
}
 
// 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
"include": [
   "src/**/*"
],
// 指定一个排除列表(include的反向操作)
 "exclude": [
   "demo.ts"
],
// 指定哪些文件使用该配置(属于手动一个个指定文件)
 "files": [
   "demo.ts"
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

16.2 compilerOptions 前的配置项

使用 tsc -init 可以初始化生成 tsconfig.json 文件了

tsconfig.json

{
  // 指定一个匹配列表(属于自动指定该路径下的所有 ts 相关文件)
  "include": [
    "src/**/*"
  ],
  // 指定一个排除列表(include 的反向操作)
  "exclude": [
    "demo.ts"
  ],
  // 指定哪些文件使用该配置(属于手动一个个指定文件)
  "files": [
    "demo.ts"
  ],

  // 不常用,继承 xx.json 的配置文件
  "extends": "./config/base"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

16.3 compilerOptions 配置

其中 rootDir 是更改根目录。例如,在 "outDir": "./dest" 配置情况下,src/index.ts 编译后输出位置为 dest/src/index.js。指定 rootDir: “src”。这样,根目录变成了 src,编译后输出则没有了 src 这一层

{
  "compilerOptions": {
    // 用来指定 ts 被编译的 es 版本
    "target": "ESNext",
    // 是否对 js 文件进行编译,默认是 false
    "allowJs": true,
    // 是否移除注释,默认 false
    "removeComments": true,
    // 编译文件的目录
    "rootDir": './src',
    // 指定编译后文件所在的目录
    "outDir": "./dist",
    // 代码源文件
    "source-map": true,
    // 所有严格检查的总开关,设置为 true 就能一键开启 4 个严格模式
  	"strict": true,
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

17.namespace 命名空间

在工作中无法避免全局变量造成的污染,TypeScript 提供了 namespace 避免这个问题

  • 内部模块,主要用于组织代码,避免命名冲突
  • 命名空间内的类默认私有
  • 通过 export 暴露
  • 通过 namespace 关键字定义

17.1 全局变量污染的解决办法

同一个目录下,有 1.ts2.ts,其中前者有代码 const a = 1,后者有代码 const a = 2,这时候会发生报错,因为全局变量中有两个相同的变量

解决办法一:将其中一个 ts 文件的 a 变量进行导出,使之成为一个模块:export const a = 1

解决方法二:使用 namespace:

namespace A {
  export const a = 1
}
console.log(A.a) // 1
1
2
3
4

17.2 嵌套、抽离、别名、合并

namespace 嵌套

namespace A {
  export namespace C {
    export const D = 5
  }
}
console.log(A.C.D) // 5
1
2
3
4
5
6

抽离 namespace

a.ts:

export namespace B {
  export const a = 2
}
1
2
3

b.ts

import { B } from './a'
console.log(B) // { a: 2 }
1
2

给 namespace 起别名

namespace A {
  export namespace C {
    export const D = 5
  }
}
import AAA = A.C
console.log(AAA.D) // 5
1
2
3
4
5
6
7

命名空间的合并

namespace A {
  export const b = 2
}
// 和 interface 一样,重名就会合并
namespace A {
  export const d = 3
}
console.log(A.b) // 2
console.log(A.d) // 3
1
2
3
4
5
6
7
8
9

18.三斜线指令

18.1 path="xxx.ts"

三斜线引用告诉编译器在编译过程中要引入的额外的文件。可以把它理解能 import,它可以告诉编译器在编译过程中要引入的额外的文件

基本使用

1.ts

namespace A {
  export const a = 1
}
1
2
3

2.ts

///<reference path="1.ts" />
namespace A {
  export const c = 666
}
console.log(A.a) // 1
console.log(A.c) // 666
1
2
3
4
5
6

18.2 types="xxx.ts"

  1. 安装:yarn add @types/node
  2. 在 ts 文件中,可以引入上面安装的包:///<reference types="node" />

19.声明文件

19.1 案例引入

tsconfig.json 设置了 "module": "CommonJS"

  1. 安装:yarn add axios express
  2. 引入成功:import axios from 'axios'
  3. 引入失败:import express from 'express',提示:尝试使用 npm i --save-dev @types/express (如果存在),或者添加一个包含 declare module 'express'; 的新声明(.d.ts)文件

解决方法一

  1. 根目录下创建 express.d.ts,里面写入:
declare var express:() => any
1
  1. index.ts 就可以调用了:
express()
1

解决方法二

  1. 安装:yarn add @types/express
  2. index.ts 就可以调用了:
import express from 'express'
1

19.2 总结上面

npm 社区收录的 ts 声明文件的下载:npm (npmjs.com)open in new window

  1. axios 已经指定了声明文件 所以没有报错可以直接用。它已经通过语法 declare 暴露声明的 axios 对象:declare const axios: AxiosStatic
  2. 有一些第三方包没有声明文件,就可以去 npm 社区去寻找它的包。这里通过 vscoode 的提示可以知道,可以用 名称.d.ts 创建一个文件去声明,例如 express.d.ts(可以直接通过 yarn add @types/express 去安装该声明文件)

20.Mixins 混入

20.1 对象混入

Object.assign 可以合并对象

interface Name {
  name: string
}
interface Age {
  age: number
}
interface Sex {
  sex: number
}

let a: Name = { name: 'yuanke' }
let b: Age = { age: 22 }
let c: Sex = { sex: 1 }

let obj = Object.assign(a, b, c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

20.2 类的混入

在 TypeScript 中,可以根据不同的功能定义多个可复用的类,它们将作为 mixins。因为 extends 只支持继承一个父类,我们可以通过 implements 来连接多个 mixins,并且使用原型链连接子类的方法和父类的方法。就像组件拼合一样,由一堆细粒度的 mixins 快速搭建起一个功能强大的类

class A {
  type!: boolean
  changeType(): void {
    this.type = !this.type
  }
}

class B {
  name!: string
  getName(): string {
    return this.name
  }
}

// implement 是继承接口的,在这里将类作为接口使用
class C implements A, B {
  type: boolean = false
  name: string = 'yuanke'
  changeType!: () => void
  getName!: () => string
}

mixins(C, [A, B])
function mixins(curClas: any, itemCls: any[]) {
  itemCls.forEach(item => {
    Object.getOwnPropertyNames(item.prototype).forEach(name => {
      curClas.prototype[name] = item.prototype[name]
    })
  })
}

let ccc = new C()
console.log(ccc.type) // false
ccc.changeType()
console.log(ccc.type) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

21.装饰器 Decorator

21.1 类装饰器

正常定义

const watcher: ClassDecorator = (target: Function) => {
  target.prototype.getName = <T>(name: T): T => {
    return name
  }
}

@watcher
class A {

}

@watcher
class B {

}

let a = new A()
let b = new B()
console.log((<any>a).getName('123')) // 123
console.log((<any>b).getName('456')) // 456
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

类装饰器工厂

const watcher = (name: string): ClassDecorator => {
  return (target: Function) => {
    target.prototype.getNames = () => {
      return name
    }
  }
}

@watcher('yuanke')
class A {

}

let a = new A()
console.log((<any>a).getNames()) // yuanke
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

组合式装饰器

const watcher = (name: string): ClassDecorator => {
  return (target: Function) => {
    target.prototype.getNames = () => {
      return name
    }
  }
}

const log: ClassDecorator = (target: Function) => {
  target.prototype.a = 213
}

@log
@watcher('yuanke')
class A {
}

let a = new A()
console.log((<any>a).getNames()) // yuanke
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

21.2 类装饰器的应用

使用类装饰器实现统一消息响应

const MessageDecorator: ClassDecorator = (target: Function) => {
  target.prototype.message = (content: string) => {
    console.log(content)
  }
}

@MessageDecorator
class LoginController {
  public login() {
    console.log('登陆业务处理')
    this.message('恭喜你,登录成功了')
  }
}
new LoginController().login()

@MessageDecorator
class ArticleController {
  public store() {
    this.message('文章添加成功')
  }
}
new ArticleController().store()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

类装饰器工厂应用

const MusicDecoratorFactory = (type: string): ClassDecorator => {
  console.log(type)
  switch (type) {
    case 'Tank':
      return (target: Function) => {
        target.prototype.playMusic = (): void => {
          console.log('播放劲爆音乐')
        }
      }
    default:
      return (target: Function) => {
        target.prototype.playMusic = (): void => {
          console.log('播放舒缓音乐')
        }
      }
  }
}

@MusicDecoratorFactory('Tank')
class Tank { }
const t = new Tank()
  ; (<any>t).playMusic()


@MusicDecoratorFactory('Player')
class Player {
}
new Player().playMusic()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

21.3 方法装饰器

参数是什么

const showDecorator: MethodDecorator = (...args: any[]) => {
  console.log(args) // 查看 args 的值
}
class User {
  @showDecorator
  public show() { }
}

// [
//   { show: [Function (anonymous)] },
//   'show',
//   {
//     value: [Function (anonymous)],
//     writable: true,
//     enumerable: true,
//     configurable: true
//   }
// ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

21.4 方法装饰器的应用

使用装饰器实现文本高亮

1.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
</head>

<body>
  <script src="./1.js"></script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12

1.ts

const highlightDecorator: MethodDecorator = (target: Object, propertyKey: string | symbol, 
descriptor: PropertyDescriptor) => {
  const method = descriptor.value
  descriptor.value = () => {
    return `<div style="color:red;">${method()}</div>`
  }
}

class User {
  @highlightDecorator
  public response() {
    return 'houdunren.com'
  }
}

console.log(new User().response()) // <div style="color:red;">houdunren.com</div>
document.body.insertAdjacentHTML('beforeend', new User().response())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

装饰器工厂控制延迟时间

// const sleepDecorator: MethodDecorator = (target: xxxx, descriptor: xxxx) => {}
const sleepDecorator = (times: number): MethodDecorator => {
  return (...args: any[]) => {
    const [, , descriptor] = args
    // 延迟 2s 后执行方法
    const method = descriptor.value
    descriptor.value = () => {
      setTimeout(() => {
        method()
      }, times);
    }
  }
}

class User {
  @sleepDecorator(2000)
  public response() {
    console.log('houdunren')
  }
}

new User().response()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

21.5 属性装饰器 & 参数装饰器

属性装饰器

  1. 属性装饰器的第一个参数 target,如果我们是给静态属性添加的属性装饰器,那么 target 就是构造函数,如果是普通属性,那么 target 就是原型对象
  2. 属性装饰器的第二个参数 propertyKey 是属性名称
const LowerDecorator: PropertyDecorator = (target: Object, propertyKey: string | symbol) => {
  Object.defineProperty(target, propertyKey, {
    get: () => {
      return '后盾人'
    }
  })
}

class Hd {
  @LowerDecorator
  public static title: string | undefined
  @LowerDecorator
  public noStaticName: string | undefined
}

console.log(Hd.title) // 后盾人
console.log(new Hd().noStaticName) // 后盾人
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

参数装饰器

  1. 第一个参数与上同,第二个参数是参数所在的方法的名称,第三个是参数所在的位置
const ParamsDecorator: ParameterDecorator = (...args: any[]) => {
  console.log(args)
  // [ {}, 'show', 1 ]
}

class Hd {
  public show(id: number = 1, @ParamsDecorator content: string) {}
}
1
2
3
4
5
6
7
8

4.14 属性装饰器的应用

将属性值变小写

const LowerDecorator: PropertyDecorator = (target: Object, propertyKey: string | symbol) => {
  let value: string
  Reflect.defineProperty(target, propertyKey, {
    get() {
      return value.toLowerCase()
    },
    set(v) {
      value = v
    }
  })
}

class Hd {
  @LowerDecorator
  public title: string | undefined
}

const obj = new Hd()
obj.title = 'HouDunRen'
console.log(obj.title) // houdunren
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

创建随机色块

const RandomColorDecorator: PropertyDecorator = (target: object, PropertyKey: string | symbol) => {
  const colors: string[] = ['red', 'blue', 'yellow', 'green', '#383838']
  Reflect.defineProperty(target, PropertyKey, {
    get() {
      return colors[Math.floor(Math.random() * colors.length)]
    }
  })
}

class Hd {
  @RandomColorDecorator
  public color: string | undefined

  public draw() {
    document.body.insertAdjacentHTML('beforeend',
      `<div style="height: 200px;width: 200px;background-color :${this.color}">houdunren.com</div>`
    )
  }
}

new Hd().draw()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

22.打包操作

22.1 Rollup 构建 TS 项目

rollup 插件:rollup/awesome: ⚡️ Delightful Rollup Plugins, Packages, and Resources (github.com)open in new window

rollup官网:rollup详细使用教程 - 掘金 (rollupjs.org)open in new window

前期准备

  1. 初始化:yarn init -ytsc --init
  2. 安装 Rollup:yarn global add rollup
  3. 新建 publicsrc 目录,前者添加 index.html,后者添加 index.ts
  4. 新建 rollup.config.js
  5. 安装 typescript:yarn add typescript
  6. 安装 TypeScript 转换器:yarn add rollup-plugin-typescript2
  7. 安装代码压缩插件:yarn add rollup-plugin-terser
  8. 安装 rollupweb 服务:yarn add rollup-plugin-serve
  9. 安装热更新:yarn add rollup-plugin-livereload
  10. 安装外部依赖:yarn add rollup-plugin-node-resolve
  11. 安装配置环境变量用来区分本地和生产:yarn add cross-env
  12. 替换环境变量给浏览器使用:yarn add rollup-plugin-replace

配置 package.json

-c:启用配置文件、-w:实时监控

{
  "name": "typescript",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "dev": "cross-env NODE_ENV=development rollup -c -w",
    "build": "cross-env NODE_ENV=production rollup -c"
  },
  "dependencies": {
    "cross-env": "^7.0.3",
    "rollup-plugin-livereload": "^2.0.5",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-replace": "^2.2.0",
    "rollup-plugin-serve": "^1.1.0",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-typescript2": "^0.31.2",
    "typescript": "^4.6.4"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

配置 rollup.config.js

console.log(process.env) // yarn dev 后打印 development,yarn build 后打印 production
import path from 'path'
import ts from 'rollup-plugin-typescript2'
import serve from 'rollup-plugin-serve'
import livereload from 'rollup-plugin-livereload'
import { terser } from 'rollup-plugin-terser'
import replace from 'rollup-plugin-replace'
const isDev = () => {
  return process.env.NODE_ENV === 'development'
}

export default {
  input: './src/index.ts',
  output: {
    file: path.resolve(__dirname, './lib/index.js'),
    format: 'umd',
    // tsconfig.json 也要开同名选项
    sourcemap: true
  },
  plugins: [
    // 支持 ts 转化
    ts(),
    // 热更新
    isDev() && livereload(),
    // 压缩代码
    terser({
      compress: {
        // 帮助删除 console.log() 内容
        drop_console: true
      }
    }),
    // 将 nodejs 里的 process.env 注册到浏览器,使浏览器也能使用
    replace({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    }),
    // 开启本地服务
    isDev() && serve({
      open: true,
      port: 1988,
      openPage: '/public/index.html'
    })
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

22.2 webpack 构建 TS 项目

安装依赖

  1. 安装 webpack:yarn add webpack webpack-cli
  2. 编译 TS:yarn add ts-loader
  3. 热更新服务:yarn add webpack-dev-server
  4. HTML 模板:yarn add html-webpack-plugin
  5. 安装 typescript:yarn add typescript

package.json 配置

{
  "name": "typescript",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "dev": "webpack-dev-server",
    "build": "webpack"
  },
  "dependencies": {
    "html-webpack-plugin": "^5.5.0",
    "ts-loader": "^9.3.0",
    "typescript": "^4.6.4",
    "webpack": "^5.72.0",
    "webpack-cli": "^4.9.2",
    "webpack-dev-server": "^4.9.0"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

webpack.config.js 配置

const path = require('path')
const htmlwebpackplugin = require('html-webpack-plugin')
module.exports = {
  entry: './src/index.ts',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index.js'
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader'
      }
    ]
  },
  devServer: {
    port: 1988
  },
  resolve: {
    extensions: ['.js', '.ts']
  },
  plugins: [
    new htmlwebpackplugin({
      template: './public/index.html'
    })
  ],
  mode: 'development'
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

23.编写发布订阅模式

23.1 代码

看懂就很牛了

interface Evenet {
  on: (name: string, fn: Function) => void
  emit: (name: string, ...args: Array<any>) => void
  off: (name: string, fn: Function) => void
  once: (name: string, fn: Function) => void
}

interface List {
  [key: string]: Array<Function>
}

class Dispatch implements Evenet {
  list: List
  constructor() {
    this.list = {}
  }
  // on 的作用就是将函数作为 value 推入数组中,第一个参数为 key 值
  on(name: string, fn: Function) {
    // list 格式:list: { name: [fun1, fun2, ...] }
    const callback = this.list[name] || []
    callback.push(fn)
    // 将新增了 fn 的数组覆盖到原数组中
    this.list[name] = callback
  }

  // emit 的作用是:执行被推入的函数
  emit(name: string, ...args: Array<any>) {
    let eventName = this.list[name]
    if (eventName) {
      eventName.forEach(fn => {
        fn.apply(this, args)
      })
    } else {
      console.error(`名称错误${name}`)
    }
  }
  off(name: string, fn: Function) {
    let eventName = this.list[name]
    if (eventName && fn) {
      let index = eventName.findIndex(fns => fns === fn)
      eventName.splice(index, 1)
      console.log(eventName)
    } else {
      console.error(`名称错误${name}`)
    }
  }
  once(name: string, fn: Function) {
    let de = (...args: Array<any>) => {
      fn.apply(this, args)
      this.off(name, de)
    }
    this.on(name, de)
  }
}

const o = new Dispatch()
o.on('post', (...args: Array<any>) => {
  console.log(args, 1)
})

// const fn = (...args: Array<any>) => {
//   console.log(args, 2)
// }
// o.on('post', fn)

// o.off('post', fn)

o.once('post', (...args: Array<any>) => {
  console.log(args, 'once')
})
o.emit('post', 1, false, { name: 'yuanke' })
o.emit('post', 2, true, { name: 'yuanke' })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

24.进阶

24.1 proxy & Reflect

基本的使用

type Person = {
  name: string,
  age: number,
  text: string
}

// 哎,这里还是用了两个 any,不知道有没有大神能去掉这俩 any?
const proxy = (object: any, key: any) => {
  return new Proxy(object, {
    get(target, prop, receiver) {
      console.log('=====>get', prop)
      return Reflect.get(target, prop, receiver)
    },
    set(target, prop, value, receiver) {
      console.log('=====>set', prop)
      return Reflect.set(target, prop, value, receiver)
    }
  })
}

const logAccess = <T>(object: T, key: keyof T): T => {
  return proxy(object, key)
}

let man: Person = logAccess({
  name: 'yuanke',
  age: 22,
  text: '太空人'
}, 'name')

let man2 = logAccess({
  name: 'yuanke2'
}, 'name')

man.age = 30 // =====>set age
console.log(man.age) // =====>get age \n 30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

24.2 Partial & Pick

Partial 源码

作用:将某个类型的所有属性值设置为可选

type Partial<T> = {
  // p in keyof T  ===>  p 为 `type key = 'name' | 'age' | 'text'` 的联合类型的遍历的成员
  // p 可以为 'name'、'age'、'text'
  // T 在这里为 Person
  [P in keyof T]?: T[P]
}
1
2
3
4
5
6

Pick 源码

将某个类型的一个或多个属性摘出来作为一个新的类型

type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}
1
2
3

使用 Partial 处理过后

type Person = {
  name: string,
  age: number,
  text: string
}

type p = Partial<Person>
// 处理过后的 p:
// type p = {
//   name?: string | undefined;
//   age?: number | undefined;
//   text?: string | undefined;
// }
1
2
3
4
5
6
7
8
9
10
11
12
13

使用 Pick 处理过后

type Person = {
  name: string,
  age: number,
  text: string
}
type p = Pick<Person, 'age' | 'name'>

// 处理过后
// type p = {
//   age: number
//   name: string
// }
1
2
3
4
5
6
7
8
9
10
11
12

24.3 Record & Readonly

Readonly 源码

作用:将某个类型的所有属性设置为 readonly

type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}
1
2
3

Record 源码

type Record<K extends keyof any, T> = {
  [P in K]: T
}

// keyof any 会返回以下的联合类型
// type key = string | number | symbol
1
2
3
4
5
6

使用 Readonly 处理过后

type R<T> = {
  readonly [P in keyof T]: T[P]
}

type Person = {
  name: string,
  age: number,
  text: string
}

type man = R<Person> // 类似 Array<string>

// man 的类型变成:
// type man = {
//   readonly name: string
//   readonly age: number
//   readonly text: string
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

使用 Record 处理过后

type Person = {
  name: string,
  age: number,
  text: string
}
type K = 'A' | 'B' | 'C'
type B = Record<K, Person>

// 类型 B 如下:
// type B = {
//   A: Person
//   B: Person
//   C: Person
// }

let obj:B = {
  A: { name: 'yuanke', age: 22, text: 'haha' },
  B: { name: 'hryh', age: 23, text: 'df' },
  C: { name: 'dfg', age: 24, text: 'hasfsaha' }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

24.4 infer

定义一个类型,如果是数组类型就返回数组元素的类型,否则就传入什么类型,就返回什么类型

传统做法:定义一个泛型 T,使之继承数组类型,如果 T 真为数组,则返回数组成员,否则返回 T 原类型本身

type TYPE<T> = T extends Array<any> ? T[number] : T
type A = TYPE<string[]> // type A = string
type B = TYPE<(string | number)[]> // type B = string | number
type C = TYPE<boolean> // type C = boolean
1
2
3
4

infer 做法

// infer 起到占位符的作用,一般用到 extends 后面。其中 U 表示数组本身
type TYPE<T> = T extends Array<infer U> ? U : T
type A = TYPE<string[]> // type A = string
type B = TYPE<(string | number)[]> // type B = string | number
type C = TYPE<boolean> // type C = boolean
1
2
3
4
5

infer 妙用:将元祖类型转为联合类型

// infer 妙用:将元祖类型转为联合类型
// U 为 string 且 number 都要存在,故被解析为了联合类型 |
type TYPE<T> = T extends Array<infer U> ? U : never

type T = [string, number] 

type uni = TYPE<T>
1
2
3
4
5
6
7