Typescript 推理不适用于来自高阶函数的类型参数

Typescript 推理不适用于来自高阶函数的类型参数,typescript,Typescript,我已经精简了这个问题的代码。这是一本书 我有一个函数,它返回一个带有类型参数的函数: 导出函数createInteractor(名称:string){ 返回(规格:InteractorSpecification)=>{ 常量结果:未知={} 将结果返回为未知的InteractiorType; } } interactiorspecification类型查找名为locators的属性,该属性具有动态字段,由用户添加 导出类型交互规范= { 选择器?:字符串; 定位器:记录; }; 高阶函数返回

我已经精简了这个问题的代码。这是一本书

我有一个函数,它返回一个带有类型参数的函数:

导出函数createInteractor(名称:string){
返回(规格:InteractorSpecification)=>{
常量结果:未知={}
将结果返回为未知的InteractiorType;
}
}
interactiorspecification
类型查找名为locators的属性,该属性具有动态字段,由用户添加

导出类型交互规范=
{
选择器?:字符串;
定位器:记录;
};
高阶函数返回此类型

将结果返回为未知的InteractiorType;
interactiortype
loook如下:

导出类型定位器实现=
{[K in keyof S['locators']]:(值:string)=>interactitorinstance}
导出类型InteractiorType=LocatorImplementation;
基本上,它从原始
规范
位置
获取属性,并将它们映射到返回类型

高阶函数的类型参数不会被推断,但如果我传入显式类型参数,它会工作:

const Link=createinteractitor('Link'))({
选择器:“a”,
定位器:{
byThis:(element)=>element.href,
by:(element)=>element.title
},
});
//不推断类型
//常量链接:定位器实现
Link.byThat('foo');
Link.byThis('bar'))
常数规格={
选择器:“a”,
定位器:{
按此:(element:htmlinklement)=>element.href,
byThat:(element:htmlinklement)=>element.title
},
asfsdfsd:“我还需要捕捉未知道具”
}
//使用显式类型
常量F=createInteractor(“链接”)(规范)
F.byThat(“foo”);
F.byThis(“酒吧”);

推理失败源于
createInteractor
,是泛型使用不当的结果

function createInteractor<E extends Element>(name: string) {
  return <S extends InteractorSpecification<E, S>>(specification: InteractorSpecification<E, S>) => {
    const result: unknown = {}
    return result as unknown as InteractorType<E, typeof specification>;
  }
}
这将强制执行相同的约束,但将
S
与值
specification
关联,以便从调用站点传递的
specification
参数推断S,并且,关键的是,其约束中的
S
也从该参数推断出
extends interactiorspecification
,最终确定
定位器
和定位器类型

虽然这一更改足以解决您遇到的特定问题,但在
createinteractitor
中,泛型再次引发了一个相关问题,但这次是在类型参数
E

无论何时,只要有一个类型参数与任何函数参数的类型无关,就应该进行双重处理。它通常是伪装成类型参数的类型断言

在我们的特定情况下,它允许我们编写如下代码

const F = createInteractor<HTMLLinkElement>('input')(spec)
我们现在可以这样写了

const Link = createInteractor('link')({
  selector: 'a',
  locators: {
    byThis: (element) => element.href,
    byThat: (element) => element.title
  },
});

// type is inferred 

Link.byThat('foo');
Link.byThis('bar')
安全和简洁,同时我们希望现在收到一个错误

const badSpec = {
  selector: 'a',
  locators: {
    byThis: (element: HTMLInputElement) => element.value, 
    byThat: (element: HTMLLinkElement) => element.title
  },
}
// error is correctly given for incompatible locator 
const bad = createInteractor('link')(badSpec);

谢谢您的回答。
createinteractitor
name
参数实际上与元素类型无关。在实际代码中,它只是用于调试,所以它可以是任何东西。原始代码实际上是
(规范:S)
,但这意味着任何键都可以传递给so:`const Link=createinteractitor('Link')({selector:'a',locators:{byThis:'element)=>element.href,},sdfds:'aaa'});`对属性
sdfds
有效,这就是我将其更改为具体类型的原因。我明白了,对此表示抱歉。但是,我建议您为标记名引入第二个参数,或者使标记名成为
规范
类型的属性,这样您就不必传递显式泛型。
const Link = createInteractor('link')({
  selector: 'a',
  locators: {
    byThis: (element) => element.href,
    byThat: (element) => element.title
  },
});

// type is inferred 

Link.byThat('foo');
Link.byThis('bar')
const badSpec = {
  selector: 'a',
  locators: {
    byThis: (element: HTMLInputElement) => element.value, 
    byThat: (element: HTMLLinkElement) => element.title
  },
}
// error is correctly given for incompatible locator 
const bad = createInteractor('link')(badSpec);