如何将TypeScript对象数组映射函数改造为通用泛型函数
泛化实现:数组转指定结构对象的TypeScript方案
针对你提出的三个场景,以下是更简洁、类型安全的泛化实现,解决你遇到的类型错误问题:
基础类型复用
先复用你定义的基础类型,确保所有可映射的资产都符合结构要求:
type NamedAsset = SanityDocument & { id: Slug }; // Slug需包含`current`字符串属性
场景1:仅泛化目标映射类型(Pic类型固定)
最低需求实现,固定输入为Pic数组,仅泛化输出的映射结构:
function mapPicToMap<V extends Record<string, Pic>>(items: Pic[]): V { return items.reduce((acc, pic) => { const key = pic.id.current as keyof V; acc[key] = pic; return acc; }, {} as V); }
使用示例
type PicMap = { alpha: Pic; bravo: Pic }; const picMap: PicMap = mapPicToMap(pics);
说明:用Record<string, Pic>约束泛型V,确保输出对象的键为字符串、值为Pic,类型断言仅用于关联业务中已知匹配的键,安全且简洁。
场景2:同时泛化输入类型和目标映射类型
支持任意符合NamedAsset的输入类型,同时泛化输出映射结构:
function mapAssetsToMap<T extends NamedAsset, V extends Record<string, T>>(items: T[]): V { return items.reduce((acc, asset) => { const key = asset.id.current as keyof V; acc[key] = asset; return acc; }, {} as V); }
使用示例
// 假设有另一个资产类型 type Video = SanityDocument & { id: Slug; duration: number }; const videos: Video[] = await sanityClient.fetch(`*[_type == "video"]`); // 定义目标映射类型 type VideoMap = { intro: Video; demo: Video }; const videoMap: VideoMap = mapAssetsToMap(videos);
说明:泛型T约束输入项必须包含id.current,V约束输出结构的类型与输入一致,完全复用且类型安全。
场景3:自动推导映射类型,无需显式定义
无需提前声明映射类型,让TypeScript自动根据数组元素的id.current推导输出结构:
function mapAssetsToAutoMap<T extends NamedAsset>(items: readonly T[]): { [K in T['id']['current']]: T extends { id: { current: K } } ? T : never; } { return items.reduce((acc, asset) => { acc[asset.id.current as keyof typeof acc] = asset; return acc; }, {} as any); }
使用示例
// 假设pics数组的id.current分别为"alpha"和"bravo" const picMap = mapAssetsToAutoMap(pics); // 自动推导类型为:{ alpha: Pic; bravo: Pic } // 直接访问picMap.alpha时,类型自动识别为Pic
说明:利用索引类型和条件类型自动生成键值对,初始值的any仅用于reduce内部,外部返回的类型是严格推导的,无需手动定义映射类型,兼顾便捷性和类型安全。
关于你遇到的类型错误
你之前的泛型函数出现「Type 'K' is not assignable」错误,是因为泛型参数T extends K[]的定义导致类型推导混乱,且V与K之间没有建立有效的类型约束关系。上面的方案通过Record<string, T>明确了输出结构与输入类型的关联,彻底解决了类型不兼容问题。
对比ChatGPT的方案,以上实现更简洁,避免了复杂的Extract语法,同时保持了类型推导的准确性。
内容的提问来源于stack exchange,提问作者Brent Arias
相关产品推荐
相关产品推荐

