Typescript 父类对子类的类型保护

yat*_*ern 4 inheritance types instanceof parent-child typescript

我有一个抽象类FooObject。只有两个孩子会继承FooObject,让我们称他们为ThisFooObjectThatFooObject

我正在寻找一种将 FooObject 变成任何子项的高性能方法。在这种情况下,这意味着不使用instanceof- 它以 <10% 的布尔查找速度执行

我调查的方式是这样的:

FooObject.ts

import ThisObject from "./ThisObject";
import ThatObject from "./ThatObject";

abstract class FooObject {

  isThisObject() : this is ThisFooObject {
    // Either check for ThisFooObject properties, or override this
    // in the ThisFooObject class to always return true.
    return duckTypingTest();
  }

  // ditto here
  isThatObject() : this is ThatFooObject {}
}
Run Code Online (Sandbox Code Playgroud)

我明白这并不漂亮。如果我有更多 FooObject 的孩子,这不是一种可扩展的方法 - 它要求父母以一种封装破坏的方式了解孩子。但在我的具体情况下,如果它避免我不得不做'instanceof',我愿意忍受这个。

从好的方面来说,当我有一个 FooObject 的引用,并且当它是一个 ThatFooObject 时我想要一些东西,我可以做这样的事情:

fooObjects.forEach( foo => {
  // Foo currently typed as a FooObject
  if(foo.isThatObject()){
    // Foo now typed as a ThatFooObject
    foo.magic+=10
  }
});
Run Code Online (Sandbox Code Playgroud)

这都是有效的 - 有一点需要注意 - 我必须小心使导入顺序正确。IE,在我的main.ts入口点,做类似的事情

import 'ThatFooObject';
import 'FooObject';
Run Code Online (Sandbox Code Playgroud)

但除此之外,我对这种模式相当满意。当我和一个以上的孩子一起尝试这个时,问题就来了。我开始遇到循环依赖问题,以及诸如class extends value undefined is not a constructor or null.

我不知道如何进行这项工作。我是否只是在错误的树上吠叫,我应该以更简单的方式做到这一点?

jca*_*alz 6

这样的事情怎么样,它不需要类相互了解:

计划是给每个具体的构造函数一个字符串属性typeId,我们将使用它来区分子类。这是FooObject构造函数的形状:

interface FooObjectConstructor<T extends FooObject> {
  new(...args: any[]): T
  typeId: string;
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以定义抽象FooObject类:

abstract class FooObject {

  // note 1
  // abstract static readonly typeId: string;  

  // note 2
  readonly typeId = (this.constructor as FooObjectConstructor<this>).typeId; 

  // note 3
  instanceOf<T extends FooObject>(ctor: FooObjectConstructor<T>): this is T {
    return this.typeId === ctor.typeId
  }
}
Run Code Online (Sandbox Code Playgroud)
  1. TypeScript 不允许抽象静态属性,所以我不能说abstract static typeId,如果我的子类没有实现它,编译器会警告我。所以我们只需要记住自己做。

  2. 我们将typeId在创建时将静态复制到每个实例,以便实例可以访问this.typeId而不是this.constructor.typeId. 我不知道如果这有助于表现不多,但你可以离开了这一点,并替换引用this.typeIdthis.constructor.typeId如果你想无处不在。

  3. instanceOf方法是通用的,接受一个FooObject构造函数,并将typeId当前对象typeId的 与传入的构造函数的 进行比较。它是通用的这一事实帮助我们避免父类需要了解每个子类,并避免您在上面遇到的疯狂网络。

好的,让我们创建那些具体的子类。我们必须记住将静态typeId设置为唯一值:

class ThisObject extends FooObject {
  static readonly typeId = "ThisObject" // don't forget this
  thisMethod() {
    console.log("This!")
  }      
}

class ThatObject extends FooObject {
  static readonly typeId = "ThatObject" // don't forget this
  thatMethod() {
    console.log("That!")
  }
}
Run Code Online (Sandbox Code Playgroud)

好的,是时候测试一下了!

declare const someFooObject: FooObject;

if (someFooObject.instanceOf(ThisObject)) {
  someFooObject.thisMethod();
} else if (someFooObject.instanceOf(ThatObject)) {
  someFooObject.thatMethod();
} else {
  // some other type of FooObject, I guess
  console.log("I wasn't expecting that");
}
Run Code Online (Sandbox Code Playgroud)

上面的所有代码都不会对我产生编译警告,并且在运行时对我有用

希望有所帮助;祝你好运!