網站搜尋

TypeScript 多型返回類型


假設我們想要實現資料類型層次結構,應該使用哪種類型?

interface Collection<T> {
  map(f: (v: T) => T): ???
}

interface Array<T> extends Collection<T> {
  map(f: (v: T) => T): ??? { return new Array<T>() }
}

interface LinkedList<T> extends Collection<T> {
  map(f: (v: T) => T): ??? { return new LinkedList<T>() }
}

多型 this 無法運作

interface Collection<T> {
  map(f: (v: T) => T): this
}

interface Array<T> extends Collection<T> {
  map(f: (v: T) => T): this { return new Array<T> }
  // ^ error `Error: type Array<T> is not assignable to type this`
}

答案#1

多態 this 的誠實使用是當你實際上返回 this 時。考慮以下類別

export interface Collection<T> {
  map(f: (v: T) => T): this
}

class Array<T> implements Collection<T> {
  map(f: (v: T) => T): this { return new Array<T>() as this; }
}

class MyArray<T> extends Array<T>{ }

new MyArray().map(o=> true) instanceof MyArray // false, but map returns MyArray

沒有任何東西強制 MyArray 覆寫 map 並返回正確的實例,所以當在 MyArray 上調用 map 時,我們仍然得到 Array 的實例(我可能會說這有點令人驚訝)。

話雖如此,在類型系統中沒有好的方法來建模這個問題,那就是一個類,在派生時必須實現特定方法,而該類本身不抽象。

你可以使用多態的 this 並利用 constructor 屬性來防範上述情況,但沒有辦法限制衍生的建構函數不能有參數(如下面的呼叫所需)。所以這個解決方案並不是完全類型安全的,而且確實使用了一些類型斷言來完成工作,但我認為它已經是最安全的做法了:

 interface Collection<T> {
  map(f: (v: T) => T): this
}

class Array<T> implements Collection<T> {
  map(f: (v: T) => T): this { return new (this.constructor as any)(); }
}

class MyArray<T> extends Array<T>{ }

console.log(new MyArray().map(o=> true) instanceof MyArray) // ture

答案#2

如果我理解正確,你所尋找的是 f 邊界多型(“重載 this”)。

interface Collection<T> {
  map(this: Collection<T>, f: (v: T) => T): this;
}

this 的類型總是用作第一個參數。注意我們可以添加更多的重載。例如,若要將 Collection<number> 轉換為 Collection<string>,我們可以這樣做:

interface Collection<T> {
  map(this: Collection<T>, f: (v: T) => T): this;
  map<U>(this: Collection<T>, f: (v: T) => U): Collection<U>;
}

用法:

declare const collection: Collection<number>;

collection.map(x => x.toString())