TypeScript泛型函数中带索引签名的子类型正确索引方法
错误原因
在泛型函数中直接写const multiObj = obj.a时,TypeScript会将multiObj的类型推断为基类型A['a'],而非泛型约束的T['a']。此时multiObj[k]的类型是{ aa: string },但AInstance<T>实际代表T['a']中索引对应的更具体子类型(因为T可以是A的子类型,比如T['a']可能包含特定字符串键对应的更窄类型)。基类型的{ aa: string }无法赋值给更具体的子类型,因此触发报错。
正确处理方式
最直接且通用的方法是显式指定multiObj的类型为T['a']:
type A = { a: { [key: string]: { aa: string; } } }; type AInstance<T extends A> = T['a'][string]; function foo<T extends A>(obj: T): AInstance<T> | null { const multiObj: T['a'] = obj.a; // 显式约束类型为泛型子类型 for (const k in multiObj) { if (multiObj[k].aa == 'aa') return multiObj[k]; // 类型匹配成功 } return null; }
两种临时解决方法的原理
Workaround 1(显式声明类型)
通过const multiObj: T['a'] = obj.a,强制TypeScript保留multiObj的泛型子类型信息,而非拓宽到基类型A['a']。此时multiObj[k]的类型会被推断为T['a'][string](即AInstance<T>),类型完全匹配,不会报错。这种方法通用性极强,适用于所有需要保留泛型子类型的场景。Workaround 2(修改索引签名和AInstance)
把索引签名改成[key: string | number],同时AInstance<T>用T['a'][number],本质是利用TypeScript对数字索引的特殊处理:当对象同时存在字符串和数字索引时,数字索引的类型会被视为字符串索引类型的子类型。但这种方法仅适用于键可能是数字的场景,通用性不如第一种,若实际场景中键只有字符串,修改属于冗余操作。
内容的提问来源于stack exchange,提问作者Serge

