与 TypeScript 中未定义的协方差



下面的代码显然输入不正确,但我不能在上面打字稿错误。 我打开了strict,以及strictNullChecksstrictFunctionTypes,但 TS 仍然坚定不移地坚信这段代码很好而且花花公子。

abstract class A {
// You can pass an undefined to any A
public abstract foo(_x: number | undefined);
}
class B extends A {
// B is an A, but it prohibits passing in an undefined.
// Note that if we did `x: string`, TS would flag it as
// an error.
public foo(x: number) {
if (x === undefined) {
throw new Error("Type error!");
}
}
}
function makeAnA(): A {
// This typechecks correct, so B is clearly an A, in
// TS's opinion.
return new B();
}
function test() {
const b = makeAnA();
// b is a B, so this should not be possible
b.foo(undefined);
}

这是预期行为吗? 我可以打开一些选项将其标记为错误吗? 我不止一次被这咬过。

这是一个设计决策。所有方法参数的行为都是双变量的。这意味着就 ts 而言,对于方法(_x: number) => void是 to(_x: number | number) => void的子类型(反之亦然)。这显然是不合理的。

最初,不仅方法参数的行为是双变量的,而且所有函数签名参数的行为都是双变量的。为了解决此问题,在打字稿 2.6 中添加了strictFuctionTypes标志。来自公关:

通过此 PR,我们引入了 --strictFunctionType 模式,在该模式下,函数类型参数位置被逆变而不是双变量检查。更严格的检查适用于所有函数类型,但源自方法或构造函数声明的函数类型除外。专门排除方法,以确保泛型类和接口(如 Array)继续以协变方式关联。严格检查方法的影响将是一个更大的重大变化,因为大量的泛型类型将变得不变(即便如此,我们可能会继续探索这种更严格的模式)。

(突出显示已添加)

因此,在这里,我们可以一瞥让方法参数继续以双变量方式相关的决策。这是为了方便。如果没有这种不健全性,大多数类将是不变的。例如,如果Array是不变的,Array<Dog>就不会是Array<Animal>的子类型,在非常基本的代码中产生各种痛点。

虽然绝对不等效,但如果我们使用函数字段而不是方法(打开strictFunctionTypes),我们确实会收到一个错误,Type '(x: number) => void' is not assignable to type '(_x: number | undefined) => void'

abstract class A {
// You can pass an undefined to any A
public foo!: (_x: number | undefined) => void;
}
class B extends A {
// Error here
public foo: (x: number) => void = x => {
if (x === undefined) {
throw new Error("Type error!");
}
}
}
function makeAnA(): A {
//And here 
return new B();
}
function test() {
const b = makeAnA();
// b is a B, so this should not be possible
b.foo(undefined);
}

游乐场链接

注意:上面的代码仅在strictFunctionTypes时给出错误,因为没有它,所有函数参数将继续以双变量方式运行。

最新更新