网站搜索

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())