深入理解Typescript中的类型和构造器签名new

这两天重看ts基础部分的interface和泛型,涉及到构造器签名部分,看得还是比较模糊,仔细再整理一下,这次应该是清晰了。

变量类型限定,主要用在以下场景:限定普通变量类型,限定函数类型(也属于限定变量类型),限定函数返回值类型,限定类类型。这里从限定普通变量类型开始,重点讲下限定类类型,即函数构造器签名。

1,限定普通变量类型

这是最简单和常用的类型限定用法,比如:

(这部分代码也是后续演示代码的基础部分,后续用到这三个类的地方,定义都在这里)

// 这部分代码也是后续演示代码的基础部分,后续用到这三个类的地方,定义都在这里
class Person {
    name: string; // 限定普通变量类型
    age: number; // 限定普通变量类型
    // 限定普通变量类型
    constructor(n:string, age:number){
        this.name = n;
        this.age = age;
    }
    run():void {
        console.log(`Person.run :: enter, name = ${this.name}`)
    }
}

class Student extends Person {
    run():void {
        console.log(`Student.run :: enter, name = ${this.name} `)
        super.run();
    }
}
class Teacher extends Person {
    run():void {
        console.log(`Teacher.run :: enter, name = ${this.name} `)
        super.run();
    }
}

现在假如我们有一个需求,需要实现一个工厂方法,根据传入的类去实例化该类,你会怎么做。

很简单吧,我也不假思索的写了一个:

function createInst(clazz: Person): Person{
   return new clazz("YXX", 18); 
   // This expression is not constructable. 
   // Type 'Person' has no construct signatures.    
}

结果呢,直接就报错了。。。

一定注意,上述方法定义中的方法参数(clazz: Person),指的是clazz是Person类型的实例,所以new去实例化的时候,直接报错

那应该怎么做呢,这里就该用到构造器签名限定啦!

2,构造器签名简述

我理解的构造器签名,就是描述函数构造器的函数签名,可以以字面量方式写,也可以定义为interface

2.1,字面量方式构造器签名:

// 使用字面量构造器签名,可以这样写。
 const myClass1: new (n:string, a:number) => Person = Student;

2.2,接口字面量方式构造器签名:

// 也可以这样写(接口字面量形式)
const myClass2: {new (n:string, a:number) : Person} = Teacher;

测试一下上述定义的变量

function test2() {
     // 在参数类型中,使用构造器签名,可以这样写。
    const myClass1: new (n:string, a:number) => Person = Student;
    // 也可以这样写(接口字面量形式)
    const myClass2: {new (n:string, a:number) : Person} = Teacher;
    
    const inst1: Person = new myClass1("student11", 18);
    inst1.run();
    // 等同于
    const inst2: Person = new Student("student22", 18);
    inst2.run();

    const inst3: Person = new myClass2("Teacher33",28);
    inst3.run();
    // 等同于
    const inst4: Person = new Teacher("Teacher44",32);
    inst4.run();
}

// 输出:
Student.run :: enter, name = student11 
Person.run :: enter, name = student11
Student.run :: enter, name = student22
Person.run :: enter, name = student22
Teacher.run :: enter, name = Teacher33
Person.run :: enter, name = Teacher33
Teacher.run :: enter, name = Teacher44
Person.run :: enter, name = Teacher44

2.3,用接口interface定义构造器签名

语法也很简单,但是需要用到new来定义:

// 接口中定义构造器(构造函数签名)
interface MyInterface1 {
    new ();
}
interface MyInterface2 {
    new (name: string, age: number);
}
interface MyInterface3 {
    new (name: string, age: number): Person;
}
interface MyInterface4<T> {
     // 这里是会有问题的,语法上正确,但是真实是无法使用的。
    // 因为无法创建 MyInterface4 类型实例,因为MyInterface4 无法被实现。。。
    new (name: string, age: number): T;
}

基于上述定义,测试一下:

function test3() {
    const myClass5: MyInterface2 = Student;
    const myClass6: MyInterface3 = Teacher;
    const inst5: Person = new myClass5("Student55",17);
    const inst6: Person = new myClass6("Teacher66",32);
    inst5.run()
    inst6.run();
}

3,使用构造器签名

上面简单陈述了【构造器签名】的语法,但是上面的应用场景纯粹是为了演示的,真实项目中不会这样写,那么【构造器签名】有用吗,主要用途在哪里呢?

个人觉得,主要的用途会在一些工厂方法中,在工厂方法中限定参数类型为构造器或者指定构造器。比如:

3.1,普通工厂方法,创建给定类的实例

// 使用字面量方式的构造器签名
function createInstNormal(clazz: new(name:string, age:number) => Person) : Person {
    return new clazz("Tom", 20);
}

// 使用接口对象字面量形式的构造器签名:
function createInstNormal2(clazz: {new(name:string, age:number) : Person}) : Person {
    return new clazz("Jim", 20);
}

// 使用接口形式的构造器签名:
function createInstNormal3(clazz: MyInterface2) : Person {
    return new clazz("Marry", 20);
}

function testCreateInst() {
    const inst1:Person = createInstNormal(Student);
    inst1.run()
    const inst2:Person = createInstNormal2(Teacher);
    inst2.run()
    const inst3:Person = createInstNormal3(Teacher)
    inst3.run()
    // 因为使用的参数类型限定,下面这一行直接会报错。
   // const inst4:Person = createInstNormal2("Teacher")
}

上面的工厂函数已经可用了,但是还不够通用,到这里,咱们可以加上泛型了。

3.2,泛型工厂方法,创建更通用的给定类的实例

// 上面的工厂函数已经可用了,但是还不够通用,到这里,咱们可以加上泛型了。
function createInstanceGeneric<T>(clazz:{new(name: string, age:number) : T} , name: string, age:number): T {
    return new clazz(name,age);
}
// 或者:
function createInstanceGeneric2<T>(clazz: new(name: string, age:number) => T , name: string, age:number): T {
    return new clazz(name,age);
}
// 或者:
function createInstanceGeneric3<T>(clazz: MyInterface2 , name: string = "defaultName", age:number = 18): T {
    return new clazz(name,age);
}

可以看到,泛型工厂方法就通用多了,传入的类只要签名满足要就好

function testCreateInstanceGeneric() {
    const inst1:Person = createInstanceGeneric(Student, "s1", 16);
    inst1.run()
    const inst2:Person = createInstanceGeneric2(Teacher, "t1", 36);
    inst2.run()
    const inst3:Person = createInstanceGeneric3(Teacher)
    inst3.run()
    const inst4:TempClass1 = createInstanceGeneric2(TempClass2, "h11", 111)
    inst4.run()
    const inst5:TempClass3 = createInstanceGeneric3(TempClass3)
    inst5.show()
    const inst6:TempClass3 = createInstanceGeneric3(TempClass3)
    inst6.show()

    // 如果不使用泛型工厂方法,就会报错
    // Argument of type 'typeof TempClass1' is not assignable to parameter of type 'new (name: string, age: number) => Person'
    const inst7:TempClass1 = createInstNormal(TempClass1) // 报错
    inst7.run()
}

3.3,注意:定义了构造器签名的接口无法被实现

interface GemericInterface {
    // 定义了构造器签名
    new(name: string, age: number);
    show(info: string):void;
}
// 下面的类定义会报错:
// Class 'SuperHero' incorrectly implements interface 'GemericInterface'.
// Type 'SuperHero' provides no match for the signature 'new (name: string, age: number): any'
class SuperHero implements GemericInterface {
    constructor(name: string, age: number) {
        console.log(`SuperHero.constructor :: enter.`)
    }

    show(info: string):void{
        console.log(`SuperHero.show :: enter, info = ${info}`);
    }
}

我个人觉得这个算TS的缺陷,或者不够合理的地方。但是也当做一个点记录下来吧,毕竟这种应用场景真不多。

原因是类(的类型)由两部分组成:静态部分的类型和实例的类型。这里因为当一个类实现了一个接口时,类型检查只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。所以认为子类构造器没有按接口定义的构造器实现。

以上内容参考:

https://www.tslang.cn/docs/handbook/generics.html

https://www.tslang.cn/docs/handbook/interfaces.html

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值