You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用泛型回调处理不同事件类型时的TypeScript类型错误

问题:多事件类型的通用事件处理器类型报错

我想实现一个通用事件处理器,指定pointermove这类事件键时,TypeScript能自动推断对应的事件类型(比如PointerEvent),但传入多个不同事件时出现类型错误。代码示例如下:

export type ContainedEvent<K extends keyof HTMLElementEventMap> = {
    eventName: K;
    callback: ContainedEventCallback<K>;
};

export type ContainedEventCallback<K extends keyof HTMLElementEventMap> = (
    event: HTMLElementEventMap[K],
) => void;

export default function useContainedMultiplePhaseEvent<
    K extends keyof HTMLElementEventMap = keyof HTMLElementEventMap
>(
    el: HTMLElement,
    events: ContainedEvent<K>[],
) {
    for (const e of events) {
        el.addEventListener(e.eventName, (ev) => e.callback(ev));
    }
}

const div = document.createElement("div");
const doA: ContainedEventCallback<"pointerdown"> = (e) => {
    console.log("A");
};
const doB: ContainedEventCallback<"pointermove"> = (e) => {
    console.log("B");
};

useContainedMultiplePhaseEvent(div, [
    { eventName: "pointerdown", callback: doA },
    { eventName: "pointermove", callback: doB },
]);

原因分析

当前函数的泛型K会被推断为"pointerdown" | "pointermove"的联合类型,导致ContainedEvent<K>要求数组中每个元素的eventName必须是这个联合类型,同时callback必须能接受HTMLElementEventMap["pointerdown"] | HTMLElementEventMap["pointermove"]类型的事件。但实际传入的每个元素的callback只能处理单一事件类型,因此类型不匹配。

解决方案

修改函数泛型,让TypeScript保留数组中每个元素的具体事件类型,而不是统一成联合类型。可以通过可变元组泛型实现:

export type ContainedEvent<K extends keyof HTMLElementEventMap> = {
    eventName: K;
    callback: ContainedEventCallback<K>;
};

export type ContainedEventCallback<K extends keyof HTMLElementEventMap> = (
    event: HTMLElementEventMap[K],
) => void;

// 使用可变元组泛型,保留每个事件的具体类型
export default function useContainedMultiplePhaseEvent<
    T extends ContainedEvent<keyof HTMLElementEventMap>[]
>(
    el: HTMLElement,
    events: [...T]
) {
    for (const e of events) {
        // 直接传递callback,无需包装,类型已自动匹配
        el.addEventListener(e.eventName, e.callback);
    }
}

const div = document.createElement("div");
const doA: ContainedEventCallback<"pointerdown"> = (e) => {
    console.log("A");
};
const doB: ContainedEventCallback<"pointermove"> = (e) => {
    console.log("B");
};

useContainedMultiplePhaseEvent(div, [
    { eventName: "pointerdown", callback: doA },
    { eventName: "pointermove", callback: doB },
]);

关键改动说明

  1. 泛型改为T extends ContainedEvent<keyof HTMLElementEventMap>[],允许数组包含任意不同类型的ContainedEvent实例。
  2. 参数events使用[...T](元组展开),让TypeScript推断数组中每个元素的具体类型,而不是合并成联合类型。
  3. 移除了不必要的回调包装(ev) => e.callback(ev),直接传递e.callback即可,因为类型已经完全匹配。

这样修改后,TypeScript会正确推断每个事件对应的回调类型,同时支持传入多个不同类型的事件。

内容的提问来源于stack exchange,提问作者SandStone

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 15:27:50