In the past two days, I have reviewed the interface and generics of the basic part of ts, involving the signature part of the constructor. It is still vague. I will sort it out carefully. This time it should be clear.
Variable type qualification is mainly used in the following scenarios: limiting common variable types, limiting function types (also belonging to restricted variable types), limiting function return value types, and limiting class types. Here we start with limiting common variable types, focusing on the lower bound fixed class types, that is, function constructor signatures.
1. Limit common variable types
This is the simplest and most common type qualified usage, such as:
(this part of the code is also the basic part of the subsequent demonstration code. Where these three classes are used later, the definitions are here)
// This part of the code is also the basic part of the subsequent demonstration code. The definitions of these three classes are here class Person { name: string; // Qualify common variable types age: number; // Qualify common variable types // Qualify common variable types 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(); } }
Now, if we have a requirement to implement a factory method and instantiate the class according to the incoming class, what would you do.
Very simple, I also wrote one without thinking:
function createInst(clazz: Person): Person{ return new clazz("YXX", 18); // This expression is not constructable. // Type 'Person' has no construct signatures. }
As a result, it directly reported an error...
It must be noted that the method parameter (clazz: Person) in the above method definition means that clazz is an instance of Person type, so an error will be reported directly when new is instantiated
What should we do? We should use constructor signature qualification here!
2. Brief description of constructor signature
The constructor signature I understand is the function signature describing the function constructor, which can be written in literal form or defined as interface
2.1. Signature of literal constructor:
// Using a literal constructor signature, you can write this. const myClass1: new (n:string, a:number) => Person = Student;
2.2. Signature of interface literal mode constructor:
// It can also be written like this (interface literal form) const myClass2: {new (n:string, a:number) : Person} = Teacher;
Test the variables defined above
function test2() { // In the parameter type, the constructor signature is used, which can be written as follows. const myClass1: new (n:string, a:number) => Person = Student; // It can also be written like this (interface literal form) const myClass2: {new (n:string, a:number) : Person} = Teacher; const inst1: Person = new myClass1("student11", 18); inst1.run(); // Equivalent to const inst2: Person = new Student("student22", 18); inst2.run(); const inst3: Person = new myClass2("Teacher33",28); inst3.run(); // Equivalent to const inst4: Person = new Teacher("Teacher44",32); inst4.run(); } // Output: 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 define constructor signature with interface
The syntax is also very simple, but you need to use new to define:
// Constructor defined in interface (constructor signature) interface MyInterface1 { new (); } interface MyInterface2 { new (name: string, age: number); } interface MyInterface3 { new (name: string, age: number): Person; } interface MyInterface4<T> { // There will be problems here. It is grammatically correct, but it can't be used in reality. // Because cannot create MyInterface4 type instance, because MyInterface4 cannot be implemented... new (name: string, age: number): T; }
Based on the above definition, test the following:
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. Use constructor signature
The syntax of [constructor signature] is briefly stated above, but the above application scenario is purely for demonstration, which will not be written in real projects. Is [constructor signature] useful and what is its main purpose?
Personally, it is mainly used in some factory methods. In factory methods, the parameter type is limited to constructor or specified constructor. For example:
3.1. Ordinary factory method to create an instance of a given class
// Constructor signature using literal mode function createInstNormal(clazz: new(name:string, age:number) => Person) : Person { return new clazz("Tom", 20); } // Constructor signature in interface object literal form: function createInstNormal2(clazz: {new(name:string, age:number) : Person}) : Person { return new clazz("Jim", 20); } // Constructor signature in the form of interface: 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() // Because the parameter type used is limited, the following line will directly report an error. // const inst4:Person = createInstNormal2("Teacher") }
The above factory functions are available, but they are not universal enough. Here, we can add generics.
3.2, generic factory method to create more general instances of a given class
// The above factory functions are available, but they are not universal enough. Here, we can add generics. function createInstanceGeneric<T>(clazz:{new(name: string, age:number) : T} , name: string, age:number): T { return new clazz(name,age); } // Or: function createInstanceGeneric2<T>(clazz: new(name: string, age:number) => T , name: string, age:number): T { return new clazz(name,age); } // Or: function createInstanceGeneric3<T>(clazz: MyInterface2 , name: string = "defaultName", age:number = 18): T { return new clazz(name,age); }
As you can see, generic factory methods are much more common. As long as the signature of the incoming class is satisfied
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() // If the generic factory method is not used, an error will be reported // Argument of type 'typeof TempClass1' is not assignable to parameter of type 'new (name: string, age: number) => Person' const inst7:TempClass1 = createInstNormal(TempClass1) // report errors inst7.run() }
3.3. Note: the interface with constructor signature defined cannot be implemented
interface GemericInterface { // Constructor signature defined new(name: string, age: number); show(info: string):void; } // The following class definitions will report errors: // 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}`); } }
Personally, I think this is a defect of TS or unreasonable. But also record it as a point. After all, there are not many such application scenarios.
The reason is that the class (type) consists of two parts: the type of the static part and the type of the instance. Here, because when a class implements an interface, the type check only checks the instance part. The constructor exists in the static part of the class, so it is not within the scope of the check. Therefore, it is considered that the subclass constructor does not implement the constructor defined by the interface.
Reference to the above: