Typescript 当存在多个泛型时,从调用站点的值推断函数arg中的类型失败

Typescript 当存在多个泛型时,从调用站点的值推断函数arg中的类型失败,typescript,Typescript,在函数中,我想使用泛型和传递的运行时值推断参数的类型 它似乎在一个简单的情况下工作,但在涉及多个泛型时失败 下面是我的代码,演示了这个问题: //这个简单的例子很有效 函数fn1(参数:{ provideType:()=>Arg, consumeType:(arg:arg)=>void }):无效{ const one=args.provideType() args.consumerType(一个) } fn1({ provideType:()=>({foo:1,bar:2}),//调用站点提

在函数中,我想使用泛型和传递的运行时值推断参数的类型

它似乎在一个简单的情况下工作,但在涉及多个泛型时失败

下面是我的代码,演示了这个问题:

//这个简单的例子很有效
函数fn1(参数:{
provideType:()=>Arg,
consumeType:(arg:arg)=>void
}):无效{
const one=args.provideType()
args.consumerType(一个)
}
fn1({
provideType:()=>({foo:1,bar:2}),//调用站点提供的参数类型
用户类型:(arg)=>{
console.log(arg.foo*arg.bar)//arg具有预期的类型
}
})
//但是像这样使用多个泛型是失败的
函数fn2(args:{//extra-generic T,以相同的方式作为arg传递给提供程序
事情:T,
provideType:(thing:T)=>Arg,
consumeType:(arg:arg)=>void
}):无效{
const one=args.provideType(args.thing)
args.consumerType(一个)
}
fn2({
thing:'hello',//在调用站点为T赋值
provideType:(t)=>({foo:t,bar:2}),//这可以工作,t解析为'string'类型
用户类型:(arg)=>{
console.log(arg.foo+arg.bar)//但现在失败了,arg的类型为“未知”
}
})
为什么第二种情况不起作用

我们的目标是为一个函数定义一个接口,其中多个函数作为参数传递,其中每个函数的返回值一旦实现,就可以在所有后续函数中使用,并自动推断出类型


这可能吗?

这是TypeScript类型推断算法的设计限制;请参阅以获得规范的答案


在下面的通话中

fn2({
  thing: 'hello',
  provideType: (t) => ({ foo: t, bar: 2 }),
  consumeType: (arg) => {
    console.log(arg.foo + arg.bar) // error!
  }
})
provideType
consumerType
提供的回调需要它们的参数、
t
arg
才能成功。因此,编译器不需要从
t
arg
读取类型,而是需要为它们分配类型。同时还需要同时推断
T
Arg
泛型类型参数

编译器尝试这样做的方式是在两个阶段推断所有内容:

在第一阶段,所有需要上下文类型的回调参数都被赋予占位符“通配符”类型,编译器试图从中推断出泛型类型参数。这意味着还不能从
providedetype
consumertype
中推断任何内容,但它确实为
T
推断
字符串,因为这是
的类型。对于
Arg
,它不知道,因为它至少需要
providedetype
的返回类型的信息,但它已经推迟了评估。因此,
Arg
的推理失败,编译器返回到
未知的

在第二阶段,编译器使用这些泛型类型参数为回调参数提供类型。因此,
t
被推断为
字符串
(万岁),而
arg
被推断为
未知
(boo)

就这样,这里不再有类型推断了。这是设计上的限制。引述:

为了支持这个特定场景,我们需要额外的推理阶段,即每个上下文敏感属性值一个推理阶段,类似于我们对多个上下文敏感参数所做的。还不清楚我们是否想尝试一下,它仍然对对象文本成员的写入顺序很敏感。最终,如果没有类型推断的完全统一,我们所能做的事情是有限的


那么,你能做些什么呢?显而易见但不令人满意的解决方法是手动指定类型参数(因此只需要上下文类型):

然后你可以这样称呼它:

function fn3<T, Arg>(
  thing: T,
  provideType: (thing: T) => Arg,
  consumeType: (arg: Arg) => void
): void {
  const one = provideType(thing)
  consumeType(one)
}
fn3(
  'hello',
  (t) => ({ foo: t, bar: 2 }),
  (arg) => {
    console.log(arg.foo + arg.bar) // okay
  }
);
现在一切正常
T
被推断为
string
Arg
被推断为
{foo:string;bar:number}
。它可能不是您想要的表单,但至少您不需要调用方指定冗余类型信息



我不确定“运行时”在这个问题中是什么意思。这里所说的一切都是在编译期间在类型检查器中发生的。当编译的JavaScript脚本实际运行时,运行时间要晚得多。也许你指的是“调用站点”或其他东西,而不是运行时?不确定。说得好,我想说的是编译器的运行时,但是对于通常意义上的运行时来说,有太多的重载,这不是一个好的术语!将使用“调用站点”切换此选项谢谢您的详细解释,很高兴知道为什么这不起作用,我想我将使用“未命名参数”重构作为解决方法。
fn2({
  thing: 'hello',
  provideType: (t) => ({ foo: t, bar: 2 }),
  consumeType: (arg: { foo: string, bar: number }) => { // <-- annotate
    console.log(arg.foo + arg.bar) // okay
  }
})
function fn3<T, Arg>(
  thing: T,
  provideType: (thing: T) => Arg,
  consumeType: (arg: Arg) => void
): void {
  const one = provideType(thing)
  consumeType(one)
}
fn3(
  'hello',
  (t) => ({ foo: t, bar: 2 }),
  (arg) => {
    console.log(arg.foo + arg.bar) // okay
  }
);