我想要一个类层次结构,其中基类的某些实现允许未定义成员,而在其他实现中则绝不会如此。我一直在尝试解决如何整天问问题,因为我尝试了几种方法,并根据自己的工作遇到了不同的语言限制。我将尝试用我尝试过的方法的一个例子来总结问题:
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
我上面标记为“坏”的那一行是问题所在–因为您仍然can't narrow a union generic无法使用简单的防护来断言data
并非未定义。我当然可以写f(this.data as Foo)
,但我想避免在所有可能的地方都这样做。
我通过添加第二个泛型参数extends boolean
来短暂地处理条件类型,但是我也无法使它起作用。基本上,我自己制定的所有解决方案都会破坏示例中的三行内容之一,其中f(this.data)
,a.data.bar
始终有效,或者s.data.bar
始终无效如果尚未检查是否定义了s.data
。
答案 0 :(得分:1)
如果您愿意使用第二个类型参数来表示未定义的可能性,那么可以这样做:
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
为never
的方式进入T | never
,则多余的类型参数(T
)将消失,并且所有操作都将在派生的Always
中按预期进行类。如果我们传入undefined
,那么我们必须再次检查未定义,就像在Sometimes