推论不适用于高阶函数的类型实参

时间:2020-09-13 16:27:51

标签: typescript

对于这个问题,我已经精简了这段代码。这是playground

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

export 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>;
  }
}

InteractorSpecification类型将查找一个名为locators的属性,该属性具有动态的字段并由用户决定添加

export type InteractorSpecification<E extends Element, S extends InteractorSpecification<E, S>> = 
  {
    selector?: string;
    locators: Record<keyof S['locators'], LocatorFn<E>>;
  };

高阶函数返回此类型

return result as unknown as InteractorType<E, typeof specification>;

InteractorType看起来像这样:

export type LocatorImplementation<E extends Element, S extends InteractorSpecification<E, S>> = 
  {[K in keyof S['locators']]: (value: string) => InteractorInstance<E, S>}

export type InteractorType<E extends Element, S extends InteractorSpecification<E, S>> = LocatorImplementation<E, S>;

基本上,它从原始specification的{​​{1}}中获取属性,并将它们映射到返回类型上。

无法推断出高阶函数的type参数,但是如果我传入一个显式type参数,它将起作用:

location

1 个答案:

答案 0 :(得分:3)

推断失败源于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使其类型与外部函数中E的类型相关。这是正确且可取的,但问题是S不在可以推断出的上下文中使用。

要解决此问题,我们将内部函数更改为

<S extends InteractorSpecification<E, S>>(specification: S) => {...}

这会强制执行相同的约束,但是会将S与值specification关联,以便从调用站点为specification传递的参数(关键是{{1 }}在其约束S中也从该参数中得出,最终确定了extends InteractorSpecification<E, S>和定位符类型。

尽管这一更改足以解决您遇到的特定问题,但locator中的泛型又产生了一个相关问题,但是这次是类型参数createInteractor中的问题。

只要您的类型参数不包含在任何函数参数的类型中,就应该重做一次。通常是伪装成类型参数的类型断言。

在我们的特定情况下,它使我们可以编写类似的代码

E

即使规范应该用于const F = createInteractor<HTMLLinkElement>('input')(spec)

TypeScript的内置HtmlLinkElement中的DOM类型提供了一个称为lib.dom.d.ts的有用类型,该类型将标记名称映射到其对应的元素类型(这就是HtmlElementTagNameMap类型检查的方式)。 / p>

我们将使用此信息来防止此类无效调用,并通过采用特定标签名称并将其用于确定和传播元素类型来同时改善使用体验。

document.querySelector('input')?.value

因此我们现在可以写

function createInteractor<N extends keyof HTMLElementTagNameMap>(name: N) {
  type Tag = HTMLElementTagNameMap[N];
  return <S extends InteractorSpecification<Tag, S>>(specification: S) => {
    return {} as InteractorType<Tag, S>;
  }
}

安全,简洁,而我们现在希望收到错误消息

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

// type is inferred 

Link.byThat('foo');
Link.byThis('bar')

Playground Link