TypeScript数组与对象的类型安全迭代类型检查问题
TypeScript通用迭代方法类型检查错误修复
我编写了一个可接收数组或对象的通用迭代方法,但无法通过TypeScript的类型检查。以下是实现代码及遇到的错误:
export type IterateArg = {[key:string]:any}|any[]; export type IterateKey<T extends IterateArg> = T extends any[] ? number : keyof T; export const iterate = <Type extends IterateArg>(obj_or_array: Type, callback: (value:any,key: IterateKey<Type> )=>any) => { if ( Array.isArray(obj_or_array)) { obj_or_array.forEach(callback); // 错误#1 }else{ for (var key in obj_or_array) { if (obj_or_array.hasOwnProperty(key)) { callback(obj_or_array[key], key); // 错误#2 } } } }; function arrayCallback(value:string, key:number){} iterate(['a','b'],arrayCallback); function objectCallback(value:string, key:string){} iterate({a:'a',b:'b'},objectCallback);
错误信息
- 错误#1:参数类型“(value: any, key: IterateKey
) => any”无法赋值给类型“(value: any, index: number, array: any[]) => void”。参数“key”和“index”的类型不兼容,类型“number”无法赋值给类型“IterateKey ”。 - 错误#2:参数类型“Extract<keyof Type, string>”无法赋值给类型“IterateKey
”。类型“string & keyof Type”无法赋值给类型“IterateKey ”。
问题原因与修复方案
核心问题是TypeScript无法在分支逻辑中准确推断泛型Type的具体类型,导致IterateKey<Type>和实际分支中的类型不匹配。以下是两种可行的修复方式:
方案1:使用函数重载(推荐)
通过为数组和对象分别定义明确的重载签名,让TypeScript在调用时精准匹配参数类型:
// 定义重载签名,明确数组与对象的回调类型 export function iterate<T extends any[]>(arr: T, callback: (value: T[number], key: number) => any): void; export function iterate<T extends Record<string, any>>(obj: T, callback: (value: T[keyof T], key: keyof T) => any): void; // 实现签名,处理具体逻辑 export function iterate(objOrArray: any[] | Record<string, any>, callback: (value: any, key: number | string) => any) { if (Array.isArray(objOrArray)) { objOrArray.forEach(callback); } else { for (const key in objOrArray) { if (objOrArray.hasOwnProperty(key)) { callback(objOrArray[key], key); } } } } // 测试用例正常工作 function arrayCallback(value:string, key:number){} iterate(['a','b'],arrayCallback); function objectCallback(value:string, key:string){} iterate({a:'a',b:'b'},objectCallback);
方案2:使用类型断言辅助推断
如果想保留原有泛型结构,可以通过类型断言告诉TypeScript分支中的类型是安全的:
export type IterateArg = {[key:string]:any}|any[]; export type IterateKey<T extends IterateArg> = T extends any[] ? number : keyof T; export const iterate = <Type extends IterateArg>(obj_or_array: Type, callback: (value:any,key: IterateKey<Type> )=>any) => { if (Array.isArray(obj_or_array)) { // 断言callback符合forEach的参数类型 obj_or_array.forEach(callback as (value: any, index: number) => any); } else { for (const key in obj_or_array) { if (obj_or_array.hasOwnProperty(key)) { // 断言key符合IterateKey<Type>类型 callback(obj_or_array[key], key as IterateKey<Type>); } } } }; // 测试用例正常工作 function arrayCallback(value:string, key:number){} iterate(['a','b'],arrayCallback); function objectCallback(value:string, key:string){} iterate({a:'a',b:'b'},objectCallback);
内容的提问来源于stack exchange,提问作者user697576
相关产品推荐
相关产品推荐

