如何在TypeScript中为多态函数正确定义类型?
需求说明
myFunction接收一个同签名回调函数数组作为参数,返回一个与数组中回调签名一致的函数。调用返回函数时,会将传入的value参数传递给数组中的每个回调并执行。
初始尝试代码
type CallbackMap = { n: (value: number) => void; s: (value: string) => void; }; type MyFunction = <K extends keyof CallbackMap>( callbacks: CallbackMap[K][] ) => CallbackMap[K]; const myFunction: MyFunction = (callbacks) => (value) => { // ^^^^^ (1) for (const callback of callbacks) { callback(value); // ^^^^^ (2) } }; myFunction([(value: number) => {}])(1); // ^ (3) myFunction([(value: string) => {}])("one"); // ^^^^^ (4)
报错信息
(1) Parameter 'value' implicitly has an 'any' type.
(2) Argument of type 'any' is not assignable to parameter of type 'never'.
(3) Argument of type 'number' is not assignable to type 'never'.
(4) Argument of type 'string' is not assignable to type 'never'.
第一次重构:修正返回值类型
调整MyFunction的返回值类型,消除了(1)(3)(4)错误,但仍有剩余报错:
type MyFunction = <K extends keyof CallbackMap>( callbacks: CallbackMap[K][] ) => (value: Parameters<CallbackMap[K]>[0]) => void; const myFunction: MyFunction = (callbacks) => (value) => { for (const callback of callbacks) { callback(value); // ^^^^^ (5) } };
剩余报错
(5) Argument of type 'string | number' is not assignable to parameter of type 'never'.
Type 'string' is not assignable to type 'never'.
其他重构尝试的问题
尝试提取Callback泛型类型并重构参数定义,错误转移到了调用处:
type Callback<V> = (value: V) => void; type CallbackMap = { n: Callback<number>; s: Callback<string>; }; type MyFunction = <K extends keyof CallbackMap>( callbacks: Callback<Parameters<CallbackMap[K]>[0]>[] ) => Callback<Parameters<CallbackMap[K]>[0]>; const myFunction: MyFunction = (callbacks) => (value) => { for (const callback of callbacks) { callback(value); } }; myFunction([(value: number) => {}])(1); // ^^^^^^^^^^^^^^^^^^^^^ (6) myFunction([(value: string) => {}])("one"); // ^^^^^^^^^^^^^^^^^^^^^ (7)
调用处报错
(6) Type '(value: number) => void' is not assignable to type 'Callback<string | number>'.
Types of parameters 'value' and 'value' are incompatible.
Type 'string | number' is not assignable to type 'number'.
Type 'string' is not assignable to type 'number'.
(7) Type '(value: string) => void' is not assignable to type 'Callback<string | number>'.
Types of parameters 'value' and 'value' are incompatible.
Type 'string | number' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.
正确解决方案
核心问题是函数实现未显式声明泛型约束,导致TypeScript无法正确关联callbacks和返回函数的参数类型。只需在实现时同步添加泛型K的约束即可:
type CallbackMap = { n: (value: number) => void; s: (value: string) => void; }; type MyFunction = <K extends keyof CallbackMap>( callbacks: CallbackMap[K][] ) => CallbackMap[K]; // 实现时显式声明泛型K const myFunction: MyFunction = <K extends keyof CallbackMap>(callbacks: CallbackMap[K][]): CallbackMap[K] => { return (value) => { for (const callback of callbacks) { callback(value); } }; }; // 调用验证 myFunction([(value: number) => {}])(1); // 正常 myFunction([(value: string) => {}])("one"); // 正常
如果不需要依赖CallbackMap,也可以直接用泛型约束值类型,写法更简洁:
type Callback<V> = (value: V) => void; const myFunction = <V>(callbacks: Callback<V>[]): Callback<V> => { return (value) => { callbacks.forEach(callback => callback(value)); }; }; // 调用验证 myFunction([(value: number) => {}])(1); // 正常 myFunction([(value: string) => {}])("one"); // 正常
内容的提问来源于stack exchange,提问作者devatina11yb

