Skip to content

TS-03 泛型、类

一、泛型

泛型的简单使用

泛型就相当于是一个变量

js
// 使用T来约束类型, 后面传入的什么, 类型就是什么, 如果传入number, 则返回的是以number构成的数组
// T不是固定写法,一般使用T;
const getArray = <T>(value: T, times: number = 5): T[] => {
    return new Array(times).fill(value)
}
// 调用之前指定类型
// 如果传入string, 则会报错, string是没有length的;
getArray<number>(123, 4).map(item => item.length)

给泛型传入变量使用<>来包裹

多个泛型定义

当一个变量不够用时,就需要使用多个变量了

js
const getArray = <T, U>(value: T, params: U, times: number): Array<[T, U]> => {
    return new Array(times).fill([value, param2])
}
// 也可以不指定类型, 会根据值自动确定类型;
getArray(1,'a',3).forEach(item => {
    console.log(item[0].length) // 报错, 数字类型没有length属性
})

接口使用泛型变量

js
// 第一种
interface getArr {
    <T>(arg1: T, times: number): T[]
}

// 第二种
interface getArr<T> {  // 使用这种形式, 里面定义的都可以使用该变量了
    (arg1: T, times: number): T[],
    array: T[] // 以t构成的数组;
}

泛型约束

  1. 使用extends来约束类型,下面的例子表示传入的参数需要有length属性;(extends关键字为继承接口,当接口被继承之后,接口有的东西继承的接口也需要有)
js
interface ValueWithLength {
    length: number
}
const getArr = <T extends ValueWithLength>(arg1: T, times): T[] => {
    return new Array(times).fill(arg)
}
getArr(123) // 报错
  1. 使用extends keyof泛型约束限制值是对象里面的一员,keyof是连接符,将对象里的所有属性名连接成一个联合类型,后面高阶类型中有提到;
js
let obj = {
    a: 'a',
    b: 'b'
}
// extends keyof表示继承T,相当于obj返回的键名组成的数组, k就是其中的一员
const getProps = <T, K extends keyof T>(object: T, propName: K) => {
    return object[propName]
}
getProps(obj, 'a')
getProps(obj, 'c') // 报错, (如果不进行keyof约束, 则不会报错, 返回undefined)

泛型中的默认值

ts
function createArr<T = string>(params: T): Array<T> {
    return new Array(params)
}

二、TS中的类

定义类

定义类时,需要在类的顶部初始化实例属性,之后才能在constructor里接收参数进行赋值,否则报错;

ts
class Point {
    public x: number
    public y: number
    constructor(x: number, y:number){
        this.x = x
        this.y = y
    }
	public getPosition(){
        return `(${this.x}, ${this.y})`
    }
}

类的修饰符

  • public 公共的

  • private 私有的

    • 外部访问定义private的属性将报错,只能在类中的访问
    • 继承也无法访问私有的属性的
  • protected 受保护的

    • 与private的区别是继承的子类可以访问

    • 给constructor添加protected只能通过子类继承创建实例,自身不能创建实例

      ts
      class Parent {
          protected age: number
          protected constructor(age: number){
              this.age = age
          }
      }
      class Child extends Parent {
          constructor(age: number) {
              super(age)
          }
      }
      let p = new Parent(18) // 报错
      let c = new Child(18) // 正常

私有属性

在ts的类中,使用private表示私有的属性,ts3.8中可以使用#表示私有字段

ts
class Persion {
    private name: string;
    // or
    #name: string;
}

需要注意的是,private声明的属性可以通过Persion['name']获取,而使用#声明的,不能使用Persion['#name']获取

其他修饰符

只读属性readonly

  • 定义属性不可被更改

  • 使用readonly也是需要添加前三个修饰符的;

    ts
    class UserInfo {
        public readonly name: string
        constructor(name: string) {
            this.name = name
        }
    }

静态属性static

标识静态属性,静态属性只能通过类访问,不能通过实例访问

ts
class Parent {
    public static age: number = 18
    public static getAge(){
        return Parent.age
    }
    constructor(){}
}
console.log(Parent.getAge()) // 18

参数属性

简化定义类需要初始化属性的过程

ts
class A {
    constructor(public name: string){}
}
let a = new A('heny') // {name: 'heny'}

在参数属性前添加修改符,会自动将属性挂载到实例上面,不需要在里面定义了;

可选类属性

  • 使用问号定义可选属性
ts
class Info {
    public name: string
    public age?: number
    constructor(name: string, age?: number, public sex: string){
        this.name = name
        this.age = age
    }
}

类的存取器

调用值时访问的函数,和取值时调用的函数

  • 与es6中的get、set没有区别
  • 存取器不需要修饰符
ts
class Info {
    private _info: string
    get infoStr(){
        return this._info
    }
    set infoStr(val){
        this._info = val
    }
}
let info = new Info()
// 初始化info
info._info = 'hahaha'
console.log(info.infoStr)

抽象类

  • 使用abstract来定义抽象类

  • 定义抽象类只能继承使用,不能直接实例化

  • 在抽象类里面定义的抽象属性或方法,在继承时需要添加到继承的类中;

ts
abstract class People {
    constructor(public name: string){}
    public abstract printName(): void // 抽象方法只需要定义方法名和接收的参数以及返回值即可
}
class Man extends People {
    constructor(name: string){
        super(name)
        this.name = name
    }
    public printName(){ // 需要手动实现该方法printName
        console.log('hahaha')
    }
}
  • 使用抽象类定义存取器
ts
abstract class People {
    public abstract _name: string
    abstract get insideName(): string
    abstract set insideName(val: string) // set不能标记返回值
}

注意:抽象方法和抽象存取器都不能包含实际的代码块,只需要标记属性名,方法参数,返回值类型,存值器函数不需要标记返回值;

实例类型

所有的类都是一个类型;

ts
class People {
    constructor(public name: string){}
}
let p1: People = new People('hny') // p1可以手动指定实例类型,也是可以省略的,会自动根据后面的类型进行推断;

类实现接口

  • 使用implements实现一个接口
ts
interface Foold {
    type: string
}
class Fool implements Foold {
    public type: string
}

注意:接口检测的是该接口定义的类创建的实例,如果定义的类里面的属性不是public,而是static,则会报错,提示丢失属性

接口继承类

当接口继承类时,会继承这个类的成员,不包括去实现,也就是说,接口也会继承类的privateprotected修饰符的成员,因为这两个成员是需要实现的,而接口继承类不需要去实现,只会被它的子类实现

ts
class A {
    protected name: string
}
interface I extends A{}
class B extends A implements I { 
    // 由于继承的A定义了protected属性需要使用实例才能访问,因此需要同时继承A再实现I接口
    public name: string
}

泛型中使用类 类型

例子:

ts
const create = <T>(c: new() => T): T => {
    return new c()
}
class Info {
    public name: string
    constructor(){
        this.name = 'h'
    }
}
create(Info)

在上面的例子中,传入了一个类,返回的是一个类创建的实例,参数c的类型是new(),代表的是调用这个类的构造函数,类型就是类创建实例后的类型;

Released under the MIT License.