首页 > 解决方案 > 有时可能未定义的 Typescript 泛型参数的模式?

问题描述

我想要一个类层次结构,其中基类的某些实现允许未定义成员,而在其他情况下永远不会如此。我一整天都在尝试弄清楚如何问这个问题,因为我尝试了几种方法,并且根据我的工作遇到了不同的语言限制。我将尝试通过我尝试过的方法示例来总结问题:

function f(foo: Foo): number { return foo.bar; }

class Foo { bar: number; }

abstract class Base<T extends Foo | undefined> {
    data: T;
    public f(): number {
        if (!this.data) { return -1; }
        return f(this.data); // bad: this.data is still `Foo | undefined` not just `Foo`
    }
}

class Always extends Base<Foo> {
  constructor() { super(); this.data = new Foo(); }
}

class Sometimes extends Base<Foo | undefined> {}

let a = new Always();
let s = new Sometimes();
a.data.bar = 1; // good: no error because `a.data` must not be undefined
s.data.bar = 1; // good: compiler flags this because s.data can be undefined

上面我标记为“坏”的那一行是问题所在——因为您仍然无法缩小联合泛型的范围,因此无法使用简单的守卫来断言data不是未定义的。我当然可以写f(this.data as Foo),但如果可能的话,我想避免在任何我引用它的地方都这样做。

我通过添加第二个泛型参数来简单地使用条件类型,extends boolean但我也无法使其工作。基本上,我自己制定的所有解决方案都打破了示例中带有注释的 3 行之一 -- f(this.data)a.data.bar始终有效,或者s.data.bar如果您尚未检查s.data已定义,则始终无效。

标签: typescriptgenericsinheritance

解决方案


如果您愿意使用第二个类型参数来表示未定义的可能性,则此方法有效:

function f(foo: Foo): number { return foo.bar; }

class Foo { bar: number; }


abstract class Base<T extends Foo, TUndefiend extends undefined> {
    data!: T | TUndefiend;
    public f(): number {
        if (!this.data) { return -1; }
        return f(this.data); // ok now
    }
}

class Always extends Base<Foo, never> {
  constructor() { super(); this.data = new Foo(); }
}

class Sometimes extends Base<Foo, undefined> {}

let a = new Always();
let s = new Sometimes();
a.data.bar = 1; // good: no error because `a.data` must not be undefined
s.data.bar = 1; // good: compiler flags this because s.data can be undefined

类型保护不会缩小到类型参数,但如果联合中存在两个不同的类型参数,它似乎工作得很好。

如果我们按原样传入并且派生类中的一切都按预期工作,那么额外的类型参数 ( TUndefined) 将会消失。如果我们传入,那么我们必须再次检查未定义,如预期的那样neverT | neverTAlwaysundefinedSometimes


推荐阅读