打字稿未正确进行案例分析



假设我们要定义一个函数,该函数接受另一个函数及其参数,并输出使用该参数调用的函数。这是一个很好的例子,我们希望打字稿进行适当的案例分析。见下文。

let fns = {
A: (x: string) => 'a' + x,
B: (y: number) => 100 + y
}
type fn = typeof fns[keyof typeof fns]
const combine = <T extends fn>(fn: T, param: Parameters<T>[0]) => {
return fn(param)
}

问题是打字稿在单词参数上给了我以下错误(在带有单词返回的行上):Argument of type 'string | number' is not assignable to parameter of type 'never'. Type 'string' is not assignable to type 'never'.ts(2345)

出于某种原因,它将两种类型的输入组合成一种类型,而不是理解T只会是一种或另一种。帮助!

(使用 A 和 B 分别引用签名)

TypeScript 在这里实际上是正确的T extends fn因为这并不意味着T只能是"A"或"B"。T extends fn是一个约束,意味着T必须可分配给fn。当然,"A"和"B"可以分配给fn,但类型A | B也可以分配给fn

这意味着有人可以像这样调用你的函数:

combine<fn>(fns.A, 0);

如果 TypeScript 允许这样做,那么这个调用将是有效的!这将导致意外和不良行为。但不要担心,我有两种可能的解决方法,每种方法都有其优点和缺点。


一种是使用外部签名并像这样编写内部签名:

function combine<T extends fn>(fn: T, param: Parameters<T>[0]): ReturnType<T>;
function combine<T extends (x: unknown) => R, R>(fn: T, param: Parameters<T>[0]): R {
return fn(param);
}

它有点丑陋和不安全,但它确实可以保留原始签名。但是,就像我上面提到的,人们可以用fn类型而不是"A"或"B"来称呼它。

操场


与第一个类似,这个使用单独的重载。它比第一个稍微好一点,因为它阻止用户使用函数联合调用它,但它需要键入更多。

function combine(fn: typeof fns.A, param: string): string;
function combine(fn: typeof fns.B, param: number): number;
function combine<T extends (x: unknown) => R, R>(fn: T, param: Parameters<T>[0]): R {
return fn(param)
}

操场

可能有更好的解决方案,但我还没有喝早上的咖啡。希望您至少理解为什么这是一个错误,但是当我想到它们时,我仍然会用更好的解决方案来编辑答案。

最新更新