在打字稿映射类型中过滤 keyof 意外工作

Posted

技术标签:

【中文标题】在打字稿映射类型中过滤 keyof 意外工作【英文标题】:Filtering keyof in typescript mapped types works unexpectedly 【发布时间】:2022-01-21 05:39:10 【问题描述】:

我正在尝试提取与事件处理程序模式匹配的接口的键(使用keyof) - 即(CustomEvent) => void

我有一个似乎有效的方法,它只提取与(CustomEvent) => void 匹配的键,但是,它也提取类型为() => void 的键。

有没有办法只提取符合(CustomEvent) => void 的类型的键?

方法基于问题 Filter interface keys for a sub-list of keys based on value type 和 Typescript : Filter on keyof type parameter

在下面的例子中

export interface JayCustomEvent 

type EventHandler = (e: JayCustomEvent) => void;

type FilteredKeys<T, U> =  [P in keyof T]: P extends string ? (T[P] extends U ? P : never) : never[keyof T];

interface AComponentEvent extends JayCustomEvent 
interface AComponent 
    anEvent(e: AComponentEvent): void,
    noParamFunction(): void,
    someOtherFunction(a: number): void
    someProp: string


let a: FilteredKeys<AComponent, EventHandler> = 'anEvent'
let b: FilteredKeys<AComponent, EventHandler> = 'noParamFunction'
let c: FilteredKeys<AComponent, EventHandler> = 'someOtherFunction'
let d: FilteredKeys<AComponent, EventHandler> = 'someProp'

我希望分配 a 能够正常工作,而 bcd 不应该。 但是,分配 ab 有效,而只有 cd 无效。

【问题讨论】:

【参考方案1】:

您的地图工具看起来不错。

我想你可能会惊讶地发现functions with lower arity are subtypes of compatible higher-arity ones。考虑以下示例:

type AssignableTo<T, U> = T extends U ? true : false;

type Fn0Params = () => void;
type Fn1Param = (p: unknown) => void;

declare const ex1: AssignableTo<Fn0Params, Fn1Param>;
ex1 // true

type Fn0ParamsNever = (...args: never) => void;

declare const ex2: AssignableTo<Fn0ParamsNever, Fn1Param>;
ex2 // false

因此,通过将noParamFunction 输入为具有never 类型的参数,您可以防止将其分配给EventHandler

noParamFunction(...args: never): void

TS Playground

根据您的问题:如果您实际使用的是CustomEvents,那么您可能想要使用(event: CustomEvent&lt;JayCustomEvent&gt;) =&gt; void

【讨论】:

【参考方案2】:

我已经完全解决了我需要的问题,所以在这里发帖以帮助其他人。

根据定义

type Func0 = () => void
type Func1 = (x: any) => void
type DOMeventHandler<E> = (((this: GlobalEventHandlers, ev: E) => any) | null)

正如@jsejcksn 在他的回答中指出的那样(这是正确的),Func0 确实扩展了Func1。但是,我们可以使用它来构造一个与作为一个参数函数的对象的键完全匹配的类型。

答案就在这里,下面解释

type EventHandlerKeys<T> = 
    [P in keyof T]:
    P extends string ?
        (T[P] extends Func1 ?
            (T[P] extends Func0 ? never : P) :
            T[P] extends DOMeventHandler<any> ? P : never) :
        never
[keyof T];

诀窍是匹配Func1,它选择零参数或一个参数的两个函数。然后我们再次匹配Func0,对于零参数函数返回never,对于一参数函数返回实际属性类型。

在这里,我们还匹配 DOMEventHandler 作为另一个条件分支的额外奖励。

【讨论】:

以上是关于在打字稿映射类型中过滤 keyof 意外工作的主要内容,如果未能解决你的问题,请参考以下文章

打字稿:如何创建 Array 泛型类型,其中包含具有给定接口的每个 keyof 实例的对象

打字稿:对象和原语之间的keyof typeof联合总是永远不会

过滤掉打字稿中接口的不需要的对象属性

如何在打字稿中过滤对象数组

如何在打字稿中显示从数组中过滤的对象列表?

根据打字稿中的请求参数设置猫鼬的查询过滤器