typescript 使用'instanceof'时推断类别泛型

lhcgjxsq  于 2022-11-30  发布在  TypeScript
关注(0)|答案(3)|浏览(150)

假设我们有以下代码:

class Foo<T = number> {
  foo: T;

  constructor(foo: T) {
    this.foo = foo;
  }
}

const F: typeof Foo<number> = Foo;

let f: unknown;

if (f instanceof F) {
  f.foo; // 'any', why is this not a 'number'?
}

Playground
为什么f的类型是Foo<any>而不是Foo<number>?我可以只使用instanceof来实现吗?还是必须使用类型 predicate 函数?

uelo1irk

uelo1irk1#

你可以使用打字机防护装置,例如

function isF(isF: unknown): isF is Foo<number>{
  return isF instanceof F;
}

if (isF(f)) {
  f.foo; // Foo<number>
}
pxq42qpu

pxq42qpu2#

通过检查f instanceof F来假定fFoo<number>类型是不安全的。instanceof运算符不关心泛型,因为它们在运行时被擦除。
我们可以用new Foo("a string")示例化f而不会出现问题。f instance F将返回true,即使f的类型是Foo<string>

const F: typeof Foo<number> = Foo;

let f: unknown = new Foo("a string")

console.log(f instanceof F)
// true

if (f instanceof F) {
  console.log(f.foo);
  // "a string"
}

检查f是否为Foo<number>的 * 唯一 * 安全方法是使用类型保护,它同时检查f是否为instanceof Footypeof f.foo === "number"

const isFooNumber = (arg: unknown): arg is Foo<number> => {
  return arg instanceof Foo && typeof arg.foo === "number"
}

if (isFooNumber(f)) {
  console.log(f.foo);
}

Playground

doinxwow

doinxwow3#

正如@TobiasS.和@MuratKaragöz所指出的,instanceof运算符在运行时不能区分FooF,因此泛型不会被传递下去。
有两种方法可以解决此问题:

仅使用instanceof

我们可以创建一个新的构造函数类型,它返回Foo<number>的一个示例,而不是像我那样用typeof Foo<number>来输入F

class Foo<T = number> {
  foo: T;

  constructor(foo: T) {
    this.foo = foo;
  }
}

const F: new (...args: ConstructorParameters<typeof Foo<number>>) => Foo<number> = Foo

let f: unknown = new F(0)

if (f instanceof F) {
  f.foo; // number
}

或者我们也可以创建一个新的类F来扩展Foo<number>(这可能是最安全的,因为F在运行时不能与Foo不匹配):

class Foo<T = number> {
  foo: T;

  constructor(foo: T) {
    this.foo = foo;
  }
}

class F extends Foo<number> {
  constructor(...args: ConstructorParameters<typeof Foo<number>>) {
    super(...args)
  }
}

let f: unknown;

if (f instanceof F) {
  f.foo; // number
}

使用类型保护

参见@ Tobias S.和@MuratKaragöz的答案。

相关问题