天天看點

TypeScript中的new () => Xxx 究竟是什麼?

TypeScript中的new () => Xxx 究竟是什麼?
TypeScript中的new () => Xxx 究竟是什麼?

起因

雖然說之前有斷斷續續的學習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中,這塊的文法應該是怎樣的呢?

TypeScript中的new () => Xxx 究竟是什麼?

找到解法

由于剛接觸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執行個體化方法
  • 繼承接口
  • 泛型

#頭條創作挑戰賽# #熱門##本地達人計劃##微頭條幫我上熱門推薦#

繼續閱讀