网站搜索

类型 - TypeScript 方法重载


我有这样的代码,一个有两个重载的方法:

/**
 * Returns all keys of object that have specific value:
 * @example
 * KeysOfType<{a:1, b:2, c:1}, 1> == 'a' | 'c'
 */
type KeysOfType<MapT extends Record<string, any>, ValT> = {
    [K in keyof MapT]: MapT[K] extends ValT ? K : never;
}[keyof MapT];

export class Hooks<EventsMap extends Record<string, any>> {
    fireHooks<K extends KeysOfType<EventsMap, void>>(
        event: K,
        context: void,
    ): Promise<void>;

    fireHooks<K extends keyof EventsMap>(
        event: K,
        context: EventsMap[K],
    ): Promise<void>;

    fireHooks<K extends keyof EventsMap>(event: K, context: EventsMap[K]) {
        // ...
    }
}

它应该这样使用:

type MyHooks = { 
  aaa: void; 
  bbb: { data: string } 
};
let h = new Hooks<MyHooks>();

h.fireHooks('aaa');
h.fireHooks('bbb', { data: 'data' });

有一个通用类Hooks,里面有一个方法fireHooks,它接受事件名称到上下文数据的映射,用于验证事件的上下文。有些事件可以在没有任何上下文的情况下调用,所以我希望这个方法只用一个参数调用这些事件。

一切都运作正常,但问题是这些重载看起来很丑且太过头。

如果我跳过重载,TS 在 h.fireHooks('aaa'); 上会报错

Expected 2 arguments, but got 1.ts(2554)

如果我只跳过第二个重载,这个重载似乎是实现的 100% 拷贝,TS 在 h.fireHooks('bbb', { data: 'data' }); 上会报错

Argument of type '"bbb"' is not assignable to parameter of type '"aaa"'.ts(2345)

有人能解释一下吗

  • 为什么我需要第一个重载才能跳过参数
  • 以及为什么我需要第二个重载,它似乎是多余的,因为它与实现完全相同?

答案#1

“为什么我需要第二个重载,它看起来是多余的,因为它和实现完全一样?”

当你使用重载时,你实际上无法直接调用实现。

观察:

function foo(a: string, b: string): void;
function foo(a: number, b: number): void;
function foo(a: number | string, b: number | string): void {
  //...
}

foo(1, 2) // good
foo('a', 'b') // good
foo(1, 'b') // Error: No overload matches this call.

即使 foo 的实现允许一个参数是数字,另一个是字符串,重载也不允许这样做。

所以你永远不会只有一个重载和一个实现。那两者将是相同的。

你要么有一个带有一个签名的函数,要么有两个或更多重载以及一个实现。


“为什么我需要第一个重载才能跳过参数”

// You can also leave off the argument entirely
fireHooks<K extends KeysOfType<EventsMap, void>>(
    event: K,
): Promise<void>;

因为你有两个函数签名,并且你需要为每个签名提供一个重载,因为(如上所述)你不能直接调用实现。每个签名的event类型不同,因为它们操作不同的键。