这两天重看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
|