如何在TypeScript中筛选包含指定字段的联合类型成员
从区分联合类型中筛选包含指定字段的成员
我有一个区分联合类型,其中多个成员包含一个非区分字段:
interface Foo { type: 'foo'; foo: string; } interface OptionalFoo { type: 'optionalfoo'; foo?: string; } interface RequiredNullableFoo { type: 'requirednullable'; foo: string | null | undefined; } interface Bar { type: 'bar'; bar: string; } type All = Foo | OptionalFoo | RequiredNullableFoo | Bar;
我希望定义一个类型工具UnionMembersWithField,从该联合类型中筛选出包含指定字段的成员,最终得到:
type Foos = UnionMembersWithField<All, 'foo'>; // Foos = Foo | OptionalFoo | RequiredNullableFoo;
我尝试了几种写法,但都无法完全满足需求:
// 工具类型:`keyof All` 为 `'type'`,因它是唯一公共字段,但我们希望接受联合类型任意成员的字段 type KeysOfUnion<T> = T extends T ? keyof T : never; // 该定义得到 `Foo | RequiredNullableFoo`,遗漏了 `OptionalFoo` type UnionMembersWithField<T, K extends KeysOfUnion<T>> = T extends Record<K, any> ? T : never; // 该定义因TS1170错误无法编译,但最贴合需求表达 type UnionMembersWithField<T, K extends KeysOfUnion<T>> = T extends { [K]?: any } ? T : never; // 该定义返回`never`... type UnionMembersWithField<T, K extends KeysOfUnion<T>> = Required<T> extends Record<K, any> ? T : never; // ...这超出预期,因为用辅助类型`DiscriminateUnion`重新定义后,得到 // `Required<Foo> | Required<OptionalFoo> | Required<RequiredNullableFoo>` type DiscriminateUnion<T, K extends keyof T, V extends T[K]> = T extends Record<K, V> ? T : never; type UnionMembersWithField<T, K extends KeysOfUnion<T>> = DiscriminateUnion<Required<T>, K, any>;
其余尝试要么无效果(输出类型与输入类型一致),要么退化为never。
考虑到All['foo']的类型为any(当并非所有联合成员都包含指定字段时,TypeScript无法生成合理类型),我甚至不确定是否能实现这样的类型。
解决方案
要实现这个需求,我们可以利用TypeScript的条件类型分布式特性,结合keyof判断字段是否存在(包括可选字段)。正确的UnionMembersWithField定义如下:
type KeysOfUnion<T> = T extends T ? keyof T : never; type UnionMembersWithField<T, K extends KeysOfUnion<T>> = T extends unknown ? K extends keyof T ? T : never : never;
原理说明
- 分布式条件类型:
T extends unknown会触发联合类型的分发,让每个成员单独参与判断 K extends keyof T判断当前联合成员是否包含字段K,这里的keyof T会包含可选字段(比如keyof OptionalFoo是'type' | 'foo')- 这样就能准确筛选出所有包含指定字段(包括可选和可空的必填字段)的联合成员
测试验证:
type Foos = UnionMembersWithField<All, 'foo'>; // 类型为 Foo | OptionalFoo | RequiredNullableFoo,符合预期
如果需要更灵活的参数限制,也可以让K直接接受PropertyKey类型,不需要依赖KeysOfUnion:
type UnionMembersWithField<T, K extends PropertyKey> = T extends unknown ? K extends keyof T ? T : never : never;
这样即使K不在联合的公共字段里,只要存在于某个成员中,也能正确完成筛选。
内容的提问来源于stack exchange,提问作者skelley
相关产品推荐
相关产品推荐

