TypeScript系统入门到项目实战

145 阅读15分钟

1-2 TypeScript的定义?

Typescript 是 JavaScript 的超集,它有自己的静态类型

Typescript 不能直接在浏览器或者 Node 环境下运行,需要编译为普通 js 才能执行

// 静态类型  ts中所说的类型,指的就是静态类型
let b: number = 123;
b = 456;

// 动态类型, a 的值随时可变
// let a = 123;
// a = '456'

1-3 TypeScript 带来了什么优势?

开发过程中发现潜在的问题

编辑器中能更好的提示(代码自动补充全,错误提示)

代码语义更清晰易懂,可读性更好(直观看到代码语义)

// type Point = { x: number, y: number }
interface Point { x: number, y: number }

function tsDemo(data: Point) {
  return Math.sqrt(data.x ** 2 + data.y);
}

tsDemo({ x: 1, y: 123 });

1-4 TypeScript基础环境搭建

VSCode编辑器设置

编辑器设置搜索 quote, 找到 Typescript 类别, 在 Quote Style 中选择 single

编辑器设置搜索 tab, 选择缩进为2个空格

Format On Save: true

Prettier: Single Quote : true

安装 Typescript

npm install -g typescript

编译成普通 js

tsc 文件名

执行编译后的 js 的运行结果

前面说了Typescript文件不能直接在浏览器或者node下运行,如果需要将TypeScript文件直接运行,可以安装ts-node

npm install -g ts-node

然后运行ts-node script.ts 命令

1-5 静态类型的深度理解

我们看到一个变量它是静态类型,不仅仅意味着这个变量的类型不能修改,还意味着这个变量上的属性和方法基本也就确定了

const count: number = 123 // count. 的时候下面所有属性都是number所拥有的

interface Point {
  x: number
  y: number
}
const count: Point = {
  x: 2,
  y: 4,
}
// 变量 count 是一个自定义 Point 静态类型, count 具备 Point 类型上的所有属性和方法

1-6 基础类型和对象类型

静态类型的优点: 可以更直观的判断变量或者属性的内容是什么

基础类型 number string bloean void undefind symbol null ...

const number: number = 123
const teacherName: string = 'Dell'

对象类型

const teacher: {
  name: String
  age: number
} = {
  name: 'TangShuo',
  age: 18,
}

数组类型

const numbers: number[] = [1, 2, 3]

Class 类型

class Person {}
// dell 必须是一个Person的类
const dell: Person = new Person()

函数类型, 它的返回值是数字类型

const getTotal: () => number = () => {
  return 123
}
// 等号前面是类型注解, 等号后面是函数具体实现

1-7 类型注解和类型推断

type annotation 类型注解, 我们来告诉 TS 变量是什么类型

type inference 类型推断, TS 会自动的去尝试分析变量的类型

如果 TS 能够自动分析变量类型,我们就什么也不需要做了

如果 TS 无法分析变量类型的话,我们就需要使用类型注解

使用方法

如果TS能自动分析变量类型,那么我们什么都不用做了,例如:

const firstNumber = 123
const secondNumber = 456
const total = firstNumber + secondNumber
// 这儿的 total 变量能自动分析为 number 类型

如果TS无法分析变量类型, 我们就需要使用类型注释 例如:

function getTotal(firstNumber: number, secondNumber: number): number {
  return firstNumber + secondNumber
}

const total = getTotal(1, 2)
// getTotal 函数的返回值要加上 number 类型, 因为 return 的可以是其它类型, 例如 return firstNumber + secondNumber + ''

1-8 函数相关类型

函数类型示例

// 入参即形参
function add(first: number, second: number): number {
  return first + second
}
const total = add(1, 2)

为什么要加上函数返回值的类型,因为 return 语句中可能返回其他的数据类型,例如:

function add(first: number, second: number): number {
  return first + second + ''
}
const total2 = add(1, 2)

函数返回值 void, void 是空的意思, 加在函数上的意思是--我希望这个函数它没有返回值

function sayHello(): void {
  console.log('Hello')
}

函数返回值 never, never 永远不可能执行到最后

function throwError(): never {
  throw new Error()
  console.log('123') // 这里的代码不会执行
}
throwError()

函数参数解构语法的类型注解

// 只要是解构语法, 类型注解必须写在花括号里
function add({ first, second }: { first: number; second: number }): number {
  return first + second
}

add({ first: 1, second: 2 })

1-9 基础语法复习

基础类型

// 基础类型, boolean, number, string, void, undefined, symbol, null
let count: number
count = 123

对象类型, {}, Class, function, []

// 函数参数和返回值类型注解
// 这儿的 :number 类型其实不用写, 因为 parseInt 方法可以直接推断出来
const func = (str: string): number => {
  return parseInt(str, 10)
}

// 这儿的 number 类型一定要写, 因为不写语法就不对了
const func1: (str: string) => number = (str) => {
  return parseInt(str, 10)
}

// 接受类型参数->返回类型值->返回值
// 巧记: 等号前面是参数返回类型, 等号后面后面是函数体
// 经验: 函数的参数往往需要类型注解,函数的返回值可以通过类型推断推断出来

其他类型

const date: Date = new Date()

变量有可能是多种类型的情况

// 联合类型
let temp: number | string = 123
temp = '456'

1-10 数组和元组

数组

const numberArr: number[] = [1, 2, 3]
// 表示 arrNumber 变量是一个数组,数组的每一项都是 number 类型
const stringArr: string[] = ['a', 'b', 'c'];
const stringArrOrNumberArrarr: (string | number)[] = [1, 'hello', 2]

数组存储对象类型的注解该怎么写?

const objectArr: { name: string; age: number }[] = [
  {
    name: 'dell',
    age: 18,
  },
]
// 类型别名, 将类型放到数组前面
type User = { name: string; age: number }
const objectArr1: User[] = [
  {
    name: 'dell',
    age: 18,
  },
]

元组

概念: 一个数组他的长度是固定的,他每一项的类型也是固定的

// 元组 tuple 约束数组每一项的类型
const teacherInfo: [string, string, number] = ['Dell', 'male', 28]
// csv
const teacherList: [string, string, number][] = [['dell', 'male', 19], ['sun', 'female', 26], ['jeny', 'female', 38]];

1-11 interface

一些通用的类型集合我们可以用 interface 去表示出来

interface 和 type 的区别, type 可以直接代表一个string, 而interface只能代表对象或函数

type Person = string
interface Person {
  name: 'tangshuo'
}
// interface 和 type 很类似但是又有区别
// interface 只能代表一个对象或者一个函数, type Person1 = string 可以直接代表一个基础类型
interface Person {
  readonly name: string // readonly 表示这个属性只能读,不能写
  job: string 
  age?: number // 加问号代表属性可有可无
  [propName: string]: any // 额外属性用 propName
  say(): string // 方法
}

// 一个接口继承另外一个接口
interface Teacher extends Person {
  thacher(): string
}

// 函数使用接口
interface SayHi {
  (word: string): string;
}

// type
type Person1 = {
  name: string
}

const getPersonalName = (person: Person) => {
  console.log(person.name)
}
const setPersonalName = (person: Teacher, name: string) => {
  person.name = name
}

const person = {
  name: 'dell',
  sex: 'male',
  say() {
    return 'hello'
  },
  teacher() {
    return 'teacher'
  },
}
getPersonalName(person)
getPersonalName({
  name: 'dell',
  sex: 'male',
  say() {
    return 'hello'
  },
  teacher() {
    return 'teacher'
  },
})
// 以对象文字传递的时候会进行强校验
setPersonalName(person, 'tangshuo')

const say: SayHi = (word: string) => {
  return word
}
// 一个类想使用接口做约束的时候,使用 implements 语法
class User implements Person {
  name = 'dell'
  say() {
    return 'hello'
  }
}

// 将 ts编译成原生的 js改怎么做
// tsc demo.ts  接口的作用就是在开发过程中帮助我们做语法提示的工具,编译之后会剔除掉接口这些内容

1-12 类的定义与继承

class Person {
  name = 'tangshuo'
  getName() {
    console.log(this.name)
    return this.name
  }
}
class Teacher extends Person {
  getTeacherName() {
    return 'Dell Lee'
  }
  getName() {
    // 重写父类的方法并调用父类的方法
    return super.getName() + 'Hello'
  }
}
const teachers = new Teacher()
console.log(teachers.getName())
console.log(teachers.getTeacherName())

1-13 类中的访问类型和构造器

// private, protected, public 访问类型
// public 允许在类的内外调用
// private 允许在类的内部被使用
// protected 允许在类内以及继承的子类中使用
// class Person {
//   public name: string
//   // private name: string
//   sayHi() {
//     console.log('Hi')
//   }
// }
// const person3 = new Person()
// person3.name = 'Dell Lee'
// console.log(person3.name)

class Person {
  // public name: string
  // constructor(name) {
  //   this.name = name
  // }
  // 在构造函数的参数上加上 public 等价于 public name: string + this.name = name 操作
  constructor(public name: string) {}
}

const person4 = new Person('Dell')
console.log(person4.name)

class Teacher extends Person {
  constructor(public age: number) {
    // super() 指的是调用父类的构造函数
    // 调用父类的方法还要把参数传进去
    super('dell')
  }
}

const teacher = new Teacher(28)

01-14.静态属性,Setter和Getter

getter and setter

// 类的实例如何访问私有属性?
class Person {
  constructor(private _name: string) {}
  get name() {
    return this._name + ' lee'
  }
  set name(name: string) {
    const realName = name.split(' ')[0]
    this._name = realName
  }
}

const person = new Person('dell')
console.log(person.name)
person.name = 'dell lee'
console.log(person.name)

用Typescript实现一个单例模式(一个类只允许获取一个这个类的实例)

class Demo {
  private static instance: Demo
  private constructor(public name: string) {}
  // static 属于类的方法, 而不是属于类的实例上的方法
  // public 可以不用写, 默认就是 public
  public static getInstance() {
    if (!this.instance) {
      this.instance = new Demo('tangshuo')
    }
    return this.instance
  }
}
const demo1 = Demo.getInstance()
const demo2 = Demo.getInstance()
console.log(demo1.name)
console.log(demo2.name)
// const demo2 = new Demo() // 类“Demo”的构造函数是私有的,仅可在类声明中访问, 不可能通过new Demo()的方式创建实例

01-15.抽象类.ts

抽象类是把类的一些公共(通用)的东西抽象出来, 而接口是把对象等一些东西抽离出来

// readonly
// class Person {
//   public readonly name: string;
//   constructor(name: string) {
//     this.name = name;
//   }
// }

// const person = new Person('Dell');
// person.name = 'hello';
// console.log(person.name);

// 抽象类 抽象类只能被继承,不能被实例化
// abstract class Geom {
//   width: number;
//   getType() {
//     return 'Gemo';
//   }
//   abstract getArea(): number;
// }

// class Circle extends Geom {
//   getArea() {
//     return 123;
//   }
// }

// class Square {}
// class Triangle {}

interface Person {
  name: string;
}

interface Teacher extends Person {
  teachingAge: number;
}

interface Student extends Person {
  age: number;
}

interface Driver {
  name: string;
  age: number;
}

const teacher = {
  name: 'dell',
  teachingAge: 3
};

const student = {
  name: 'lee',
  age: 18
};

const getUserInfo = (user: Person) => {
  console.log(user.name);
};

getUserInfo(teacher);
getUserInfo(student);

02-01.使用SuperAgent和类型定义文件获取页面内容

创建 package.json 文件 npm init -y

创建 tangshuo.config.json 文件 tsc --init

SuperAgent 的作用, 在 node 中发 ajax 请求获取页面内容

02-02.使用cheerio进行数据提取

cheerio: 为服务器特别定制的,快速、灵活、实施的jQuery核心实现

02-07.TypeScript的编译运转过程的进一步理解

将 ts 代码编译成 js 可使用 tsc 命令, 如果不指定路径的话会默认编译整个项目的 .ts 文件. 但是这样的话文件会很混乱, 应该将编译的文件放到一个指定文件夹下, 如 build 文件夹

实现上述功能可以配置 tsconfig.json 文件, 修改"outDir": "./"属性为 "outDir": "./build"

 "scripts": {
    "build": "tsc"
  }

如果想实现不用每次修改代码都运行npm run build 命令, 可以将代码修改为

"scripts": {
    "build": "tsc -w"
 }

nodemon 的作用: 它将监视源中的任何更改并自动重新启动服务器

只要项目文件发生变化, 就自动运行./build/crowller.js这个文件, 注意 nodemon只能监测 js 文件的变化, 所以需要两个命令配合"build": "tsc -w",, 监听项目 ts 文件的变化, 一但变化就重新编译

注意他们两个各自的职责, 当两个命令都执行的时候,tsc -w监听到变化就会运行nodemon node ./build/crowller.js命令

问题: 现在需要运行两个命令, 有什么办法只运行一个命令呢? 还真有, concurrently来了,concurrently插件可同时运行多个命令 ,如 npm run watch-js & npm run watch-less

"scripts": {
    "dev:build": "tsc -w",
    "dev:start": "nodemon node ./build/crowller.js",
    "dev": "concurrently npm:dev:*"
 }

03-01.TypeScript中的配置文件

如果直接运行tsc demo.ts 是不会以tsconfig.json配置文件进行编译, 如果想要配置文件生效, 得直接运行 tsc命令, 不能带后面的参数

tsc默认编译根目录下的.ts文件,如果只想编译某个文件, 添加 include属性, 如

{
	"include": ["./demo.ts"]
}

如果不想编译某个文件, 添加 `exclude' 属性

{
	"exclude": ["./demo.ts"]
}

compilerOptions 编译配置文件的配置项

removeComments: true // 删除注释
noImplicitAny: false // 不要求显示的设置 any
strictNullChecks: false // 是否强制校验 null 类型 列如: const teacher: string = null, 如果为 true 的话就不能将string类型的变量赋值为null
rootDir: "./src" // ts 入口文件夹
outDir: "./build" // 编译后输出文件夹
incremental: true // 增量编译, 只编译 ts 文件后面新增的内容, 之前编译的内容不再编译
allowJs: true // 是否允许将项目中的 js 文件也进行编译
checkJs: true // typescript 是会对 ts 文件的语法进行检测的, 如果想要对 js 文件也进行语法检测, 可以设置为 true
noUnusedLocals: true // 对未使用的变量进行警告提示
noUnusedParameters: true // 对未使用的函数参数进行警告提示
baseUrl: "./" // ts 项目的根路径
outFile: // 指定一个文件,将所有输出捆绑到一个JavaScript文件中

请尝试将 “lib” 编译器选项更改为“es2017”或更高版本错误

// tsconfig.json
"compilerOptions": {
  // ...
	"lib": ["es2021"]
}

03-03.联合类型和类型保护

interface Bird {
  fly: boolean
  sing: () => {}
}
interface Dog {
  fly: boolean
  bark: () => {}
}

// 类型保护
// 类型断言的方式
function trainAnimal(animal: Bird | Dog) {
  // 这儿就是联合类型, 使用 | 操作符
  // 如果直接调用 sing 或 bark 方法, Typescript 就会报错, 因为不能保证传过来的对象一定有这两个方法
  // 可以通过类型断言的方式来进行保护

  if (animal.fly) {
    ;(animal as Bird).sing()
  } else {
    ;(animal as Dog).bark()
  }
}

// in 语法做类型保护
function trainAnimal2(animal: Bird | Dog) {
  if ('sing' in animal) {
    animal.sing()
  } else {
    animal.bark()
  }
}

// typeof 语法做类型保护
function add(first: string | number, second: string | number) {
  if (typeof first === 'string' || typeof second === 'string') {
    return `${first}${second}`
  }
  return first + second
}

// instanceof 语法做类型保护
class NumberObj {
  count: number
}
function addSecond(first: string | NumberObj, second: string | NumberObj) {
  if (first instanceof NumberObj && second instanceof NumberObj) {
    return first.count + second.count
  }
}

03-04.Enum 枚举类型

enum Status {
  OFFLINE = 1,
  ONLINE,
  DELETED,
}
// 默认是从 0 开始的
console.log(Status.OFFLINE) // 1
console.log(Status.ONLINE) // 2
console.log(Status.DELETED) // 3

// 能反向映射, 根据数值反向去查枚举类型的名称
console.log(Status[1]) // OFFLINE

// const Status = {
//   OFFLINE: 0,
//   ONLINE: 1,
//   DELETED: 2
// }

// enum的 应用场景, 接受几个固定的值返回固定的结果
function getResult(status) {
  if (status === Status.OFFLINE) {
    return 'offline'
  } else if (status === Status.ONLINE) {
    return 'online'
  } else if (status === Status.DELETED) {
    return 'deleted'
  }
  return 'error'
}

const result = getResult(1)
console.log(result)

03-05.函数泛型.ts

// 泛型 泛指的类型
// 使用: 在函数括号前面加一对尖括号, 里面跟一个或多个泛型, 泛型只在用的时候才知道是什么类型, 定义的时候并不知道

function join(first: string | number, second: string | number) {
  return `${first}${second}`
}
// 现在需求是: 如果第一个参数传的是string类型, 第二个参数也必须是string类型, 如果第一个参数传的是number类型, 第二个参数也必须是number类型

function join2<T>(first: T, second: T) {
  return `${first}${second}`
}
// 先定义一个泛型 T, 它是 string 还是 number 现在还不知道, 他可以指任何东西, 先让 first 等于这个泛型, second 也等于这个泛型

// 在使用函数的时候对这个泛型进行指定
// 翻译: 调用 join2 方法的时候, 我指定的泛型它的类型是string, 这个时候 T 就变成 string 了
join2<string>('1', '1')
join2<number>(1, 2)

function map<T>(params: T[]) {
  // 等价于 params: Array<T>
  return params
}
map<string>(['hello'])

// 如何设置多个泛型呢, 假如有一个需求, 第一个参数接受的是 number, 第二参数接受的是 string
// 可以使用多个泛型, 函数上面写了多个泛型的定义, 调用的时候把具体的类型定义下就可以了
function join3<T, P>(first: T, second: P) {
  return `${first}${second}`
}
join3<number, string>(1, '2')
join3(1, '2') // 如果不写具体的定义的话, 他会根据你传入的参数进行推断具体类型是什么样子

// 可以让返回结果也等于泛型
function anotherJoin<T>(first: T, second: T): T {
  return first
}

03-06.类中的泛型以及泛型类型

// 泛型能解决使用联合类型不灵活的问题
// class DataManager {
//     constructor(private data: string[] | number[]) {}
//     getItems(index: number){
//         return this.data[index]
//     }
// }
interface Item {
  name: string
}
// 泛型必须拥有 Item 里面所有的东西
// 翻译: 我定义了一个泛型 T , 它是什么具体类型现在不知道, 他继承了类型 Item, 它未来会对应一个具体类型, 这个具体类型一定要有 Item 里所有的东西
class DataManager<T extends Item> {
  constructor(private data: T[]) {}
  getItems(index: number): string {
    return this.data[index].name
  }
}
// 实例化的时候泛型得到了具体的对应
const data = new DataManager<Item>([{ name: 'hello' }])
console.log(data.getItems(1))

// 通过 extends 对泛型进行约束
class DataManager2<T extends number | string> {
  constructor(private data: T[]) {}
  getItem(index: number): T {
    return this.data[index]
  }
}
const data2 = new DataManager2<string>([])

// 如何使用泛型作为一个具体的类型注解
function hello<T>(params: T) {
  return params
}

const func: <T>(param: T) => T = hello

03-07.命名空间-namespace(上)

namespace Home {
  class Header {
    constructor() {
      const elem = document.createElement('section')
      elem.innerText = 'This is a Header'
      document.body.appendChild(elem)
    }
  }
  class Content {
    constructor() {
      const elem = document.createElement('section')
      elem.innerText = 'This is a Content'
      document.body.appendChild(elem)
    }
  }
  class Footer {
    constructor() {
      const elem = document.createElement('section')
      elem.innerText = 'This is a Footer'
      document.body.appendChild(elem)
    }
  }

  export class Page {
    constructor() {
      new Header()
      new Content()
      new Footer()
    }
  }
}

<script>
  new Home.Page()
</script>

03-08命名空间-namespace(下)

将 ts 文件编译后合并输出到一个文件 "outFile": "./dist/page.js", 使用这个配置必须设置 moduleamd 或 system, 如: "module": "amd"

components.ts

namespace Components {
  export interface User {
    name: string
  }
  export class Header {
    constructor() {
      const elem = document.createElement('section')
      elem.innerText = 'This is a Header'
      document.body.appendChild(elem)
    }
  }
  export class Content {
    constructor() {
      const elem = document.createElement('section')
      elem.innerText = 'This is a Content'
      document.body.appendChild(elem)
    }
  }
  export class Footer {
    constructor() {
      const elem = document.createElement('section')
      elem.innerText = 'This is a Footer'
      document.body.appendChild(elem)
    }
  }
}

page.ts

// namespace 相互引用的声明
/// <reference path="./components.ts" />
namespace Home {
  export namespace Dell {
    export const teacher: Components.User = {
      name: 'dell',
    }
  }
  export class Page {
    constructor() {
      new Components.Header()
      new Components.Content()
      new Components.Footer()
    }
  }
}

调用

<script src="./dist/page.js"></script>
<script>
  new Home.Page()
</script>

4-10 import对应的模块化

ts 代码

// components.ts
export interface User {
  name: string
}
export class Header {
  constructor() {
    const elem = document.createElement('section')
    elem.innerText = 'This is a Header'
    document.body.appendChild(elem)
  }
}
export class Content {
  constructor() {
    const elem = document.createElement('section')
    elem.innerText = 'This is a Content'
    document.body.appendChild(elem)
  }
}
export class Footer {
  constructor() {
    const elem = document.createElement('section')
    elem.innerText = 'This is a Footer'
    document.body.appendChild(elem)
  }
}

// page.ts
import { Header, Content, Footer } from './components'
export default class Page {
  constructor() {
    new Header()
    new Content()
    new Footer()
  }
}

使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js" integrity="sha512-vRqhAr2wsn+/cSsyz80psBbCcqzz2GTuhGk3bq3dAyytz4J/8XwFqMjiAGFBj+WM95lHBJ9cDf87T3P8yMrY7A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="./dist/page.js"></script>
    <script>
       require(['page'], (page) => {
        new page.default()
       })
    </script>
</body>
</html>

03-11.描述文件中的全局类型(上)

jquery.d.ts

// .d.ts(类型定义文件或类型描述文件) 的作用, 帮助我们理解 js 文件 或 js 库里面的内容的

// 全局变量
declare let $: (params: () => void) => void

// 全局函数
interface JqueryInstance {
  html: (html: string) => JqueryInstance
}
// 函数重载
declare function $(readyFunc: () => void): void
// 一个函数可以有多种形式, 允许对同一个函数的名字进行多个全局函数声明
// declare function $(params: string): {
//   html: (html: string) => {}
// }
declare function $(selector: string): JqueryInstance

// 使用interface的语法实现函数重载
interface Jquery {
  (readyFunc: () => void): void
  (selector: string): JqueryInstance
}

// 如何对对象进行类型定义,以及对类进行类型定义,以及命名空间的嵌套
declare namespace $ {
  namespace fn {
    class init {}
  }
}

Page.ts

const teacher: string = 'tangshuo'
console.log(teacher)

$(function () {
  $('body').html('<div>Hello</div>')
  new $.fn.init()
})

03-13.模块代码的类型描述文件

// ES6 模块化
declare module 'jquery' {
  interface JqueryInstance {
    html: (html: string) => JqueryInstance
  }
  // 混合类型
  function $(readyFunc: () => void): void
  function $(selector: string): JqueryInstance
  namespace $ {
    namespace fn {
      class init {}
    }
  }
}

03-14.泛型中keyof语法的使用

interface Person {
  name: string
  age: number
  gender: string
}
class Teacher {
  constructor(private info: Person) {}
  // 类型保护 旧的方法
  // getInfo(key: string) {
  //   if (key === 'name' || key === 'age' || key === 'gender') {
  //     return this.info[key]
  //   } else {
  //     throw new Error(`key '${key}' 不存在于 info 对象中`)
  //   }
  // }

  // type NAME = 'name'
  // type T = 'name'
  // key = 'name'
  // Person[T]
  // 类型保护 泛型方法
  getInfo<T extends keyof Person>(key: T): Person[T] {
    return this.info[key]
  }
}
const teacher = new Teacher({ name: 'dell', age: 30, gender: 'male' })
const test = teacher.getInfo('name') // 代码提示 test 是 string 类型
const test2 = teacher.getInfo('age') // 代码提示 test2 是 number 类型
console.log(test)

05-01.类的装饰器1

// 类的装饰器
// 装饰器本身是一个函数
// 接受的参数是一个构造函数
// 装饰器通过 @ 符号来使用
// 装饰器运行时机: 类创建的时候运行,不是创建实例的时候运行

// function testDecorator(constructor: any) {
//   constructor.prototype.getName = () => {
//     console.log('DELL')
//   }
// }
function testDecorator(flag: boolean) {
  if (flag) {
    return function (constructor: any) {
      constructor.prototype.getName = () => {
        console.log('DELL')
      }
    }
  } else {
    return function (constructor: any) {}
  }
}

@testDecorator(true)
class Test {}

const test = new Test()
;(test as any).getName()

05-01.类的装饰器2

function testDecorator() {
  return function <T extends new (...args: any[]) => any>(constructor: T) {
    return class extends constructor {
      name = 'lee'
    }
  }
}

// 函数执行后接受参数
const Test = testDecorator()(
  class Test {
    name: string
    constructor(name: string) {
      this.name = name
    }
  }
)

const test = new Test('dell')
console.log('test', test.getName())