在学习Typescript时,很多人对类接口中的“类静态部分与实例部分的区别”这一章类容比较费解,这里根据实际的案例并辅以说明,帮助大家理解。
前言
关于这一部分,在官网中说的比较晦涩:
https://www.tslang.cn/docs/handbook/interfaces.html
当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内
意思是什么呢?我们将官网上的例子放在编辑器中
interface ClockConstructor {
new (hour: number, minute: number);
}
class Clock implements ClockConstructor {
currentTime: Date;
constructor(h: number, m: number) { }
}
编译不通过,报错原因为
官网给出的解释是:
这里因为当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。
由上,我们初步得出了一个初步的结论:类不能直接去实现静态部分的接口,它实际上是对实例部分做类型检查。
这个结论是什么意思,以及如何解决带来的问题,是这一篇文章要说明的重点。
应用场景及业务假设
说到类,就不得不提到javascript es6,在es6版本中首次提到class概念,但其实class只是传统构造函数的语法糖。关于es5的构造函数,相信各位老司机们已经很熟悉了,本文不再做介绍,下面来看下在实际场景中,如何使用js的class(构造函数)来解决问题。
首先来看类的应用场景:类(构造函数)主要用来在创建对象时完成对对象属性的一些初始化等操作,配合工厂模式等可以方便快捷的创建大量的实体。
因此我们假设这么一个简单的应用场景:我们作为一个老师,要录入每个学生的id(id)和年龄(age)(即创建大量的 student对象,对象中有id和age两个字段),现在我们手上已有的条件是:班级id(classId),学生学号(code),学生年龄(age),且学生id的生成规则为:班级id后接学生学号。
已知:班级id(classId),学生学号(code),学生年龄(age)
输出:student:{ id:...., age:.... }
原始class实现
class Student{
constructor(classId, code, age){
this.id = classId + "" + code
this.age = age
}
go(){
console.log('gogogo')
}
}
let 小明 = new Student('05', '33', 12)
let 小红 = new Student('05', '12', 11)
以上是在js中的实现,通过使用类Student来进行对象的创建,如果使用typescript,如何对类进行类型检查呢?
typescript约束
实例部分
首先要明白官网中所说的:静态部分与实例部分 分别是哪里。
实例部分指上面代码中的 let 小明 = new Student('05', '33', 12)中,通过new关键字创造的小明,即为实例部分。因此在typescript文档中
当一个类实现了一个接口时,只对其实例部分进行类型检查
意思就是如果我们的class继承了interface接口,那么typescript将会对new出来这个的实例进行检查(而不会去检查class内部的constructor)。
由上,当我们定义类接口并实现时,首先要考虑到的是生成实例的类型校验:
// 实例部分类型,指那些通过类实例出来的对象,要满足的部分
interface StudentInterface {
id: string
age: number
go():void;
}
这样的约束虽然看起来问题不大, 但是使用者可能会违背接口设定者的本意,在constructor中放飞自我,比如下面这个糟糕的例子:
// 默认参数
let classId = 'errorClassId'
let code = 'defaultCode'
let age = 0
// 实例部分接口
interface StudentInterface {
id: string
age: number
go():void;
}
// 错误的class:将生成逻辑写在了constructor外
class StudentItem implements StudentInterface {
id: string = classId + code;
age: number = age;
// 传入的属性值没有挂载在this上
constructor(classId: string, code: string, age: number){
console.log('I am your father')
}
go(){
console.log('gogogo')
}
}
// 由于class中constructor为空,并没有对传入的三个参数进行操作,因此入参无效
let 小明 = new StudentItem ('05', '33', 12)
console.log(小明)
打印结果为
I am your father
{
age: 0
id: "errorClassIddefaultCode
}
这显然是不可理喻的。因此在使用class的时候,我们不仅仅是要约束最终创建的实例,更重要的是要约束类中的constructor构造器,也就是 类的静态部分。
静态部分
那么如何用typescript来约束类的静态部分呢?官网上也给出了表示:
interface StudentInfoType {
// 返回类型是 实例部分 的接口
new (classId: string, code: string, age: number): StudentInterface;
}
但是仅仅这样是不够的,因为官网已经明确说明:
constructor存在于类的静态部分,所以不在检查的范围内。
因此还要配合一个构造函数,来同时进行两个部分的约束:
// 1.实例部分接口,用来约束最终创建的实例
interface StudentInterface {
id: string
age: number
go():void;
}
// 2.静态部分接口,用来约束构造器声明,返回类型为实例部分接口类型
interface StudentInfoType {
new (classId: string, code: string, age: number): StudentInterface;
}
// 3.声明用于构造对象的方法(区别于名词‘构造函数’)调用静态部分接口进行实例化对象操作
// 通过调用静态部分接口,来进行接口约束
function createStudent(studentInfo: StudentInfoType, classId: string, code:string, age: number): StudentInterface {
return new studentInfo(classId, code, age);
}
// 4.声明class,声明时实现 实例部分接口
class StudentItem implements StudentInterface {
id: string;
age: number;
// constructor构造器,内部声明在createStudent中被约束
constructor(classId: string, code: string, age: number){
this.id = classId + "" + code
this.age = age
}
go(){
console.log('gogogo')
}
}
// 5.传入构造函数来实例化对象,在createStudent中将后续参数通过constructor构造器挂载在实例上
let 小明 = createStudent(StudentItem, '05', '33', 12)
console.log(小明)
运行结果为
{
age: 12
id: "0533"
}
运行结果满足期望。
由此,使用typescript对class进行约束声明及使用,一共五个步骤(1/2顺序可以互换):
声明实例部分接口
静态部分接口,用来约束构造器声明,返回类型为实例部分接口类型
声明用于构造对象的方法,调用静态部分接口进行实例化对象操作
声明class,声明时实现 实例部分接口
传入构造函数来实例化对象
在typescript中,类的声明和使用一共经过以上五个步骤,对比于最开始的js版本的class,确实繁琐了很多,但是对类进行了严格的限制,更有利于后续的维护。
如果感觉这样写麻烦的话,在网上看到一种更为简单的使用方法:
——感谢博主 kiwi_piggy 在
Typescript类静态部分与实例部分的区别_kiwi_piggy的博客-CSDN博客_类静态部分与实例部分的区别f
Typescript类静态部分与实例部分的区别@TOC在学习typescript的时候在这一部分看了很久,特地把中文英文文档都看了一遍,在此写下自己的理解先看这个例子interface ClockConstructor { new (hour: number, minute: number);}class Clock implements ClockConstructor {/*报错:Class 'Clock' incorrectly implements interface 'Clock
https://blog.csdn.net/kiwi_piggy/article/details/108261409
中分享的方法:
interface StudentInterface {
id: string
age: number
go():void;
}
interface StudentInfoType {
new (classId: string, code: string, age: number): StudentInterface;
}
let createStudent: StudentInfoType = class StudentItem implements StudentInterface{
id: string;
age: number;
constructor(classId: string, code: string, age: number){
this.id = classId + "" + code
this.age = age
}
go(){
console.log('gogogo')
}
}
let 小明 = new createStudent('05', '33', 12)
console.log(小明)
这样一来,逻辑会更加清晰一点,具体使用哪种方式,就见仁见智了。
总结
typescript的class中,静态部分即指class中constructor以及对constructor的接口约束;实例部分是指实例化后的对象以及对实例化后对象的接口约束,在typescript中使用class时,两者结合方能对class进行一个完整的约束。
参考
接口 · TypeScript中文网 · TypeScript——JavaScript的超集 typescript官网文档
Typescript类静态部分与实例部分的区别_kiwi_piggy的博客-CSDN博客_类静态部分与实例部分的区别b