TypeScript中RemoveIndexSignature实现疑问:为何需双重条件判断?
为什么实现
RemoveIndexSignature<T>需要双重extends条件判断? 先明确需求:我们要实现一个工具类型,把对象里的索引签名(比如[key: string]: any或[key: number]: any)剔除,只保留具体定义的属性。比如:
type Foo = { [key: string]: any; bar: string; baz: number; }; // 预期 RemoveIndexSignature<Foo> = { bar: string; baz: number } type Bar = { [key: number]: any; 123: boolean; 456: string; }; // 预期 RemoveIndexSignature<Bar> = { 123: boolean; 456: string }
先看高赞的双重判断实现
常见的正确写法是这样的:
type RemoveIndexSignature<T> = { [K in keyof T as ( string extends K ? never : number extends K ? never : K )]: T[K] };
或者另一种利用模板字符串的变体:
type RemoveIndexSignature<T> = { [K in keyof T as K extends `${infer Str}` ? Str : never]: T[K] };
为什么单条件判断不行?
如果你尝试简化成单条件,比如:
// 错误实现 type RemoveIndexSignature<T> = { [K in keyof T as K extends string ? K : never]: T[K] };
会发现结果依然保留了字符串索引签名,根本没达到剔除效果。
问题出在TypeScript对索引签名和具体属性的类型区分上:
- 当对象有字符串索引签名时,
keyof T会包含string(宽泛类型)+ 具体属性的字面量(比如"bar")。 - 当对象有数字索引签名时,
keyof T会包含number(宽泛类型)+ 具体数字字面量(比如123)。
单条件K extends string只会筛选出所有属于string范畴的key——不管是宽泛的string索引,还是具体的"bar"字面量,都会被保留,自然无法剔除索引签名。
双重判断的核心逻辑
双重extends是在做反向校验,用来区分「宽泛的索引类型」和「具体的字面量属性」:
- 对于具体属性的字面量key(比如
"bar"):string extends "bar"是false(因为宽泛的string类型不能被更具体的字面量类型包含),所以会保留这个key。
- 对于宽泛的索引签名key(比如
string):string extends string是true,所以返回never,把这个索引签名剔除。
数字索引的逻辑完全一致:number extends K只有当K本身就是number类型(即数字索引签名)时才会成立,此时剔除;具体数字字面量则会被保留。
模板字符串的变体本质也是利用了同样的逻辑:宽泛的string/number类型无法被模板字符串捕获为具体的字面量,而具体属性的字面量可以被匹配,从而实现过滤。
内容的提问来源于stack exchange,提问作者Janice Zhong
相关产品推荐
相关产品推荐

