起因
虽然说之前有断断续续的学习TypeScript(后面均用TS简称)的经历,但最近才真正意义上尝试着使用进行一个项目开发。
在开发过程中,我的同事阿洋很快就遇到了这样一个问题:
我希望有一个方法,我传入类,它可以根据我传入的类进行实例化;
如果用原生JS来实现,那就是如下:
function test(fn){
const _instance = new fn();
}
由于早年搞过一些JAVA开发,我首先脑海里想出来了JAVA对应的语法:
private void test(Class clazz) throws Exception {
Object _instance = clazz.newInstance();
}
// 以下为使用
test(Cat.class)
基础类型中并没有Type、Class一类的类型,也就是说,好像并不支持直接进行Class的传递?
应该不至于,如果不支持这个特性,估计TS早就喷的一塌糊涂了;
那么在TS中,这块的语法应该是怎样的呢?
找到解法
由于刚接触TS,对其语言特性了解有限,我决定阅读开源项目的源码进行参考学习。
很快,我在nest的源码中,找到了类似的场景,并且找到了实实在在的解决办法;
export interface Type<T = any> extends Function {
new (...args: any[]): T;
}
用法如下:
function test(fn: Type):void {
const a = new Type();
}
// 以下为使用
test(Cat);
甚至,它还能支持继承;
function test(fn: Type<Animal>):void {
const a = new Type();
}
class Animal {
...
}
class Cat extends Animal {
...
}
// 以下为使用
test(Cat);
OK,找到了可以用的代码片段,但是这段代码片段是什么意思呢?
它们又用到的TS官方文档中的哪些知识点呢?
理解&学习
接下来,我需要理解一下,为什么这段代码能实现TS里的类似Java里Class的表现?
export interface Type<T = any> extends Function {
new (...args: any[]): T;
}
首先,它的定义是interface,所以需要看的第一个点,是interface;
一、 函数类型接口
在官方文档中,有这样一段描述:
为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
interface SearchFunc {
(source: string, subString: string): boolean;
}
可以确定,上面的方法所定义的,是一个函数类型接口,它支持传入的是一个函数类型;
让我们暂时抛开其他难以理解的部分,理解这个接口最核心的部分,经过删减,如下:
interface Type{
(...args: any[]): any;
}
一个真正意义上的函数类型接口,该接口的用法是指:传入的参数需要是一个函数,且需要对其参数进行类型检查;
而在本例子中,参数为 ...any[],也就是未做类型和数量的限制;说明它对方法的参数未做要求; 在此基础上,我们需要一点点拼凑出它完整的含义;那么下一个关键词便是new;
二、 关键词:new
在源代码中,对方法的描述,实际上是比示例里最简单的函数类型接口的示例,多了一个new关键词的;
export interface{
new (...args: any[]): any;
}
在官方接口里,并没有找到直接描述new ()的内容点,直接进行一个StackOverFlow,在该链接中,有人描述如下:
new() describes a constructor signature in typescript. What that means is that it describes the shape of the constructor. For instance take {new(): T; }. You are right it is a type. It is the type of a class whose constructor takes in no arguments.
翻译并且删减一哈:
new()用来描述一个构造函数;
也就是说,该接口不仅仅是指定了一个函数类型接口,而且指定了该函数类型接口,需要是一个构造器函数;
OK,关于new的这个知识点现在清楚了,那继续往下看;
上面代码里还用到了extends 关键词,所以又涉及到了第二个知识点:继承接口;
三、 继承接口
首先,让我们明确一个知识点,Function在TS里,究竟是什么?
答案是:接口;
interface Function {
/**
* Returns the name of the function. Function names are read-only and can not be changed.
*/
readonly name: string;
}
所以,这里用到的是继承接口的知识点,Type是一个继承了Function的接口,它是一个函数,且众所周知,TS里的class就是方法,且在编译后就是一个构造函数;
class A {
constructor(){}
}
const b = A instanceof Function; // true
ok,对于这里的理解我们再进一步理解;
interface Type extends Function{
(...args: any[]): any;
}
这代表了一个构造器函数接口;
四、 泛型
export interface Type<T = any> extends Function {
new (...args: any[]): T;
}
最后一个知识点,就在于其中的T是什么了。
按上面的写法,如果每次都返回any,对于TS而言,这体验显然是糟糕的;那么要怎么描述返回的类型呢?
显然,只能是用泛型了;
在官方文档中,如此描述泛型:
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
而本例,用到的则是泛型接口:
我们可能想把泛型参数当作整个接口的一个参数。 这样我们就能清楚的知道使用的具体是哪个泛型类型(比如: Dictionary<string>而不只是Dictionary)。 这样接口里的其它成员也能知道这个参数的类型了。
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
小节
小小的一段代码中,其实包含了以下几个知识点:
- 函数类型接口
- new实例化方法
- 继承接口
- 泛型
#头条创作挑战赛# #热门##本地达人计划##微头条帮我上热门推荐#