TS 常用的那几招

169 阅读8分钟

00.png

01 TS 与 JS

01.png

02 使用TS

02.png

1. 全局安装

  1. 安装: yarn global add typescript
  2. 编译: tsc first.ts

2. 项目创建时选择ts: Vue3为例子,如上图


03 TS基础类型

03.png

04.png

八个内置类型

1 number: 数值类型

    let num: number // 声明类型
    let num2 = 10 // 类型推断:根据赋值进行推断变量的类型

2 string: 字符串类型

    let str: string

3 boolean: 布尔类型

    let bol: boolean

4 undefined: 未定义

    let udf: undefined

5 null: 空值

    let nul: null

6 object: 对象类型

    let obj: object

7 bigint: 大整数类型,没有位数的限制,精确表示任何位数的整数,用n后缀表达

    let bigI: bigint = 100n

8 symbol: 唯一标识符

    let symb: symbol = Symbol('sym')

9 数组:可以使用Array 或 Type[] 表达

    let arr1: number[] = [1, 2, 3]
    let arr2: Array<number> = [1, 2, 3]

10 元组tuple(新增):表示一个已知数量和类型的数组

    let tup: [string, number] = ['hello', 1]

11 any任意类型: 尽量不适使用

    let anyT: any
    anyT = 1
    anyT = true
    anyT = '123'

12 void: 函数没有返回值,其实返回了undefined

   function voidFn(name: string): void {}
   voidFn("123") // undefined

13 never

 通常用于抛出异常或 根本就不会有返回值的函数,例如死循环的函数
     function throwErr(): never {
      throw new Error('an error')
    }
    throwErr() // Uncaught Error 
**never 和 void 的区别**
    void 可以被赋值为 null 和 undefined 的类型。 
    never 则是一个不包含值的类型。
    拥有 void 返回值类型的函数能正常运行。
    拥有 never 返回值类型的函数无法正常返回 。 

14 枚举

用键值对形式表达一个事物属性,默认key为0,依次递增

     enum Message {
      SUCCESS, // 默认从0标记
      ERROR,
      WARNING,
    }
    Message.SUCCESS // 0
    Message.ERROR // 1
    Message.WARNING // 2
    Message[0] // 'SUCCESS'

枚举也可自定义初始key,并依次递增

    enum Message2 {
      SUCCESS = 100, // 自定义初始值
      ERROR,
      WARNING,
    }
    Message.ERROR // 101
    enum Message3 {
      SUCCESS = 100, 
      ERROR = 30,
      WARNING,
    }
    Message.SUCCESS // 100
    Message.ERROR // 30
    Message.WARNING // 31
    Message[31] // 'WARNING'

枚举也可定义字符串key,称为字符串枚举

    enum Status {
      SUCCESS = '200',
      ERROR = '400',
    }

枚举使用变量或计算方法表达

    // 使用变量、计算方法
    const test = 2
    function func(): number {
      return 4
    }
    enum Status2 {
      a = 0,
      b = test,
      c = func(),
    }

枚举内部变量使用

    enum M {
      A = 'AA',
      S = 'ss',
      F = A,
    }

异构枚举:同时存在 数值和字符串

    enum R {
      A = 0,
      B = 'B',
    }

04 接口 interface: 定义对象的类型

05.png

用法

    // 用法
    interface Person {
      name: string
      age: number
    }
    let Tony: Person = {
      name: 'Tom',
      age: 10,
    } 
    // Tony属性必须拥有name 和 age,且name为字符串类型,age为number类型

可选 和 只读属性

   interface Person1 {
      readonly name: string
      age?: number
    }
    let p: Person1 = {
      name: 'Mike',
    }
    p.name = "Tom" // ts会警告 无法分配到 "name" ,因为它是只读属性。

混合类型:既有对象属性,又有函数

    interface PersonLib {
      (): void // 函数
      sex: number // 属性
      run(): void // 方法
    }
    function getPersonLib(sex: number) {
      let plib = (() => {}) as PersonLib
      plib.sex = sex || 1
      plib.run = () => {
        console.log("run!")
      }
      return plib
    }

    let pl = getPersonLib(0)
    pl()
    pl.sex // 0
    pl.run() // run!

接口继承、实现类

    interface Skill {
      skill(): void
    }
    interface Run extends Skill {
      run(): void
    }
    class Man implements Run {
      skill() {
        console.log('Skill-skill')
      }
      run() {
        console.log('Run-run')
      }
    }

多属性检测

在实际开发中,interface的属性检查往往会造成一定的麻烦,我们遇到多余属性检测时,一般采用三种方法绕开

    // 1、类型断言
    interface Person {
      name: string
      age: number
    }
    let p1: Person = {
      name: 'Mike',
      age: 20,
      sex: 0, // 多余的sex
    } as Person 
    // 2、索引签名
    interface Person2 {
      name: string
      [key: string]: string | number
    }
    let p2: Person2 = {
      name: 'Mike',
      age: 20,
      sex: 0,
    }
    //3、利用类型兼容性
    // 相当于pObj类型推断出{name: string,  age:  number, sex: number}
    // 将pObj赋值给p3,根据类型兼容性,两个变量都有name和age类型,
    // 便会认为两个变量相同,绕过属性检查
    let pObj = {
      name: 'Mike',
      age: 20,
      sex: 0,
    }
    let p3: Person = pObj

05 type 类型别名

用法

    // 格式
    type Person = {
      name: string
    }
    // 字面量类型
    type Age = string
    // 联合类型
    type Sex = string | string[] | number | number[]

    // 交叉类型: 常用于多个接口类型合并成一个类型,从而实现接口继承的效果
    type MergeType = { name: string; age: number } & { sex: number }

type 与 interface

   1、语法不同
   2、要用于扩展 extends  implaments 时用interface;
       要用字面类型、联合类型用type
   3、interface可以多次定义,会自动合并, type不能多次定义

06 Generics 泛型

定义: 指在定义函数、接口或类时,不预先指定类型,而在使用时再指定具体类型的一种特性


使用场景:

    // 在开发中当一个方法需要传入不同类型的入参数时,需要定义多次或使用any,例如:
    function getValue(val: string): string { return val}
    function getValue(val: number): number { return val}
    // 或
    function getValue(val: any): any {return any}

这样处理将使得项目代码臃肿或类型定义不规范维护性低下,在这种情况下我们可用泛型替代

    // 使用泛型 Function<Type>(value: Type): Type {}
    // 所以上面得例子可以改写成:
    function getValue<T>(value: T): T {
      return value
    }
    getValue(123) // 123
    getValue('aaa') // aaa
    // 泛型的类型名是可自定义的,多个的;
    // 不过一般我们都用T=Type,K=Key,V=Value
    function getValueTuple<CustomType, K>(value1: CustomType, value2: K): [CustomType, K] {
      return [value1, value2]
    }
    getValueTuple('aaa', 123) // ['aaa', 123], 泛型会根据入参进行类型推断
   getValueTuple<string, number>('aaa', 123) // ['aaa', 123] // 当遇到自定义类型时也可进行声明

泛型约束

extends 对泛型进行约束
interface O {
  name: string
}
 //
function getObjValue<T extends O>(obj: T): T {
  return obj
}
getObjValue() // 警告:应有 1 个参数
getObjValue({ id: 1 }) // 警告: “id”不在类型“O”
getObjValue({ id: 1, name: '123' }) // 正常

泛型类型

1 typeof

用来获取一个变量声明或属性类型
    type Identity = {
      name: string
    }
    let iden: Identity = {
      name: '',
    }
    console.log(typeof iden) // Identity
    const MyData = [
      { name: 'Alice', age: 15 },
      { name: 'Bob', age: 23 },
      { name: 'Eve', age: 38 },
    ]

    type NewPerson = typeof MyData[number]
    // 相当于 
    type NewPerson = {
        name: string
        age: number
    } 

2 keyof

用于生成对象类型键的字符串或数字 的联合类型
    type Point = { x: number; y: number }
    type P = keyof Point //  "x" | "y"
    // 如果类型 是string或number索引签名,keyof 返回的是具体类型
    type Arrayish = { [n: number]: boolean }
    type A = keyof Arrayish // number

    type Mapish = { [k: string]: boolean }
    type M = keyof Mapish // string | number
    // 虽然k这里声明了是string,但在对象中number也会被转换为string识别,  obj[0]  ==> obj['0'],所以这里是string | number

3 in

用于遍历类型
    type Keys = 'name' | 'age' | 'sex'
    type Person = {
      [k in Keys]: string
    }

3 in

用于遍历类型
    type Keys = 'name' | 'age' | 'sex'
    type Person = {
      [k in Keys]: string
    }

内置工具类型

4 Partial

将传入的属性变为可选
    interface Todo {
      title: string
      description: string
    }
    const todo: Todo = {} // 警告:const todo: Todo = {}
    type Par = Partial<Todo> // 
    /** Par的属性都变为可选的
    type Par = { 
        title?: string | undefined 
        description?: string | undefined 
    }**/
    const todoP: Partial<Todo> = {} // todoP可赋缺失Todo属性的值

5 Required

将传入的属性变为必选
    interface Todo2 {
      title?: string
      description?: string
    }
    const todo2: Todo2 = {} 
    type Req =  Required<Todo2>
    const todoP: Required<Todo2> = {} // 警告 “Required<Todo2>”中的以下属性: title, description

6 Readonly

 将传入的属性变为只读
    interface Todo3 {
      title: string
    }
    const todo3: Todo3 = { title: ''}
    todo3.title = 'to run'
    type ReadO =  Readonly<Todo3>
    const todoRO: Readonly<Todo3> = {title: ''}
    todoRO.title = 'readonly' // 初始化后不可再被修改了 所以报警告"title" 是只读属性

7 Record<Keys, Type>

 构造一个对象类型,其属性键为Keys,其属性值为Type
    type Keys7 = 'miffy' | 'boris'
    let cats: Record<Keys7, number> 
    cats = {
      miffy: 123,
      boris: 456,
    } 
    interface Type7  {
      age: number;
      breed: string;
    }
    let cats2: Record<Keys7, Type7> 
    cats2 = {
      miffy: { age: 1, breed: '短尾猫'},
      boris: { age: 1, breed: '蓝猫'},
    }

8 Pick<Type, Keys>

Type中挑选一组属性Keys
   interface Todo8 {
      title: string
      description: string
      completed: boolean
    }
    type TodoPreview = Pick<Todo8, 'title' | 'completed'>

    const todo8: TodoPreview = {
      title: 'Clean room',
      completed: false,
      // description: "123" // 这个就变多余的了
    }

9 Omit<Type, Keys>

Type中选择所有属性然后删除Keys
   interface Todo9 {
      title: string
      description: string
      completed: boolean
      createdAt: number
    }
    type TodoInfo = Omit<Todo, 'completed' | 'createdAt'>
    const todoInfo: TodoInfo = {
      title: 'title',
      description: 'description',
      // completed: "completed" // 这个变多余的了
      // createdAt: 1657595356980// 这个也变多余的了
    }

10 Exclude<UnionType, ExcludedMembers>

 从联合类型中 排除某些成员
   type T10 = Exclude<string | number | (() => void), Function>
    // 等同于:type T10 = string | number

11 Extract<Type, Union>

 从联合类型中 提取某些成员
   type T11 = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'd' | 'f'>
    // 等同于:type T11 = "a" | "d"

12 NonNullable

 过滤Type类型中的 nullundefined 类型
   type T12 = NonNullable<string[] | string | null | undefined>
    // 等同于:type T12 = string | string[]

13 InstanceType

 由构造函数类型Type的实例类型来构建一个新类型
   class C {
      x = 0
      y = 0
    }
    type T0 = InstanceType<typeof C>  // C
    const T13: T0 = {
      x: 0,
      y: 1
    }

07 TS中的类

1 class

    class Point {
      // 成员属性
      x: number // 不使用修饰符 默认是public
      y: number

      // 静态属性
      static cname: string = 'Greeter' // 实例不可用 类本身可访问

      // 修饰符
      public cx1 = 1 // 公共的
      private cx2 = 2 //私有的:只能该类本身访问
      protected cx3 = 3 // 受保护的: 只能该类及其子类可访问

      public readonly cx4 = 4 // 只读,不可更改属性值

      // 构造函数
      constructor(x: number, y: number) {
        this.x = x
        this.y = y
      }
      /** 构造函数 - 参数属性
        在构造函数中定义参数属性相当于在类中创建了该属性
      **/
      // constructor(x: number, y: number, public z: number) {
      //   this.x = x
      //   this.y = y
      //   this.y = z
      // }

      // 成员方法
      public getPoint() {
        return `${this.x}, ${this.y}`
      }

      // 静态方法: 静态方法里面的this指向的是类本身,而不是类的实例对象,所以静态方法里面只能访问类的静态属性和方法
      static getCname() {
        return this.cname
      }
    }
    const point = new Point(1, 2)
    point.getPoint() // 1, 2

2 继承 extends

    class Child extends Point {
      constructor(x: number, y: number) {
        super(x, y)
      }
      // 继承属性、方法,方法重载 不展开了
    }
    const pointChild = new Child(1, 2)
    pointChild.getPoint() //1, 2

3 抽象类

抽象方法 、抽象字段
抽象类不能被实例化,只能被继承;
抽象方法也不能在抽象类中做具体实现,只能在子类中实现且必须实现
所以一般用于抽离对象的共性,由子类继承后根据抽象类的规范进行实现逻辑,实现多态
    abstract class Person {
      name!: string
      // constructor(public name: string) {}
      abstract speak(): void
    }
    class Chinese extends Person {
      speak() {
        console.log('讲中国话')
      }
    }
    class English extends Person {
      speak() {
        console.log('English')
      }
    }
    let cc = new Chinese()
    let ee = new English()
    cc.speak()
    ee.speak()

4 接口实现 implements

    interface Phone {
      type: number
      open<T>(style: T): T
    }

    class Iphone implements Phone {
      type: number
      constructor(s: number) {
        this.type = s
      }
      open<T>(style: T) {
        return style
      }
    }
    const iphone = new Iphone(13)
    iphone.open('13pro') // 13pro

4 泛型中使用类

    const create = <T>(c: new () => T): T => {
      return new c()
    }
    class Infos {}

    create<Infos>(Infos)