起因
雖然說之前有斷斷續續的學習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執行個體化方法
- 繼承接口
- 泛型
#頭條創作挑戰賽# #熱門##本地達人計劃##微頭條幫我上熱門推薦#