TypeScript 接口函数字段逆变类型错误

TypeScript Interface Function Field Contravariance Type Error

我有以下类型,它确定所有属性都将是函数,它不接受参数,或者接受类型为 Record<string, any> 的单个参数:

type FnTrait = Record<
  string,
  (input?: Record<string, any>) => any
>;

我尝试将此类型扩展到另一个接口(我希望它具有相同的约束)。

interface HasFn extends FnTrait {
  someFn(): string; // no problem
  anotherFn(o: {id: string}): number; // ts error 2411
}

这会在 anotherFn 上产生错误:Property 'anotherFn' of type '(o: { id: string; }) => number' is not assignable to string index type '(input?: Record<string | number | symbol, any> | undefined) => any'.

为什么someFn不报错,而anotherFn报ts错误2411?看来应该允许这种缩小。

如有任何帮助,我们将不胜感激。谢谢!

这是 function types being contravariant in their parameters. The word "contravariant" means "varies in the opposite way". If you make a function's parameter more specific (narrow), you are making the function type itself more general (wide). That means instead of making HasFn a subtype of FnTrait (which is what extends means), you are sort of making it a supertype. This is not allowed, and violates the principle of substitutability 的实例。

特别是, anotherFn() 是错误的,因为它要求其参数具有 stringid 属性,而 FnTrait["anotherFn"] 则不需要。预计您可以调用 FnTrait 的任何 属性,或者不带参数,或者使用几乎任何类型的单个参数。但是如果你在没有参数的情况下调用它的 anotherFn() 方法,或者使用缺少正确排序的 id 属性 的参数,HasFn 可能会爆炸。因此,根据定义,HasFn 不可分配给 FnTrait,尽管已声明对其进行扩展:

const hasFn: HasFn = {
  someFn: () => "",
  anotherFn: o => o.id.length
}
const fnTrait: FnTrait = hasFn;
fnTrait.anotherFn({ a: 123 }); // okay at compile time, explodes at runtime

因为 anotherFn() 意味着您不能安全地替换 FnTrait 值,而 HasFn 值被请求,FnTrait 无法分配给 HasFn,你会得到一个错误。


之所以someFn()而不是是因为a function of fewer parameters is assignable to a function that takes more parameters。这是因为 someFn() 将不可避免地忽略传递给它的任何参数,因此将它视为可能接收参数的函数是安全的:

fnTrait.someFn({ a: 123 }); // okay at compile time and runtime

这与 anotherFn() 失败的原因相同:可替代性。

Playground link to code