TypeScript泛型K未被识别为联合类型的问题排查
问题场景
我编写的TypeScript泛型类型GraphSubAttributeNames在传入联合类型作为泛型参数K时,未按预期对联合类型的每个成员单独判断并合并结果,而是返回了不符合预期的单一值。代码示例如下:
type GraphSubAttributeNames<N extends ApiModelNames, K extends GraphSubAttributeKeys<N>> = GraphSubAttributesMap<N> extends never ? 'never' : GraphSubAttributesMap<N>[K] extends MapRelation ? GetGraphRelationMap<N, K> extends never ? 'case 1' : 'case 2' : 'case 3' type T1 = GraphSubAttributeNames<'artist', 'portrait'> // 'case 3', 结果正确 type T2 = GraphSubAttributeNames<'artist', 'heroArtwork'> // 'case 2', 结果正确 type T3 = GraphSubAttributeNames<'artist', 'heroArtwork' | 'portrait'> // 得到'case 3',预期应为'case 3' | 'case 2'
补充相关类型定义(我认为这些并非问题根源):
import {Artist, Footer, Site, Artwork, StrapiUser, Navigation, Exhibition, StrapiMedia} from "~/models" type UnpackArray<T> = T extends (infer A)[] ? A : T type StrapiAttributes = 'createdAt' | 'updatedAt' | 'publishedAt' | 'createdBy' | 'updatedBy' type StripeStrapiAttributes<T extends StrapiApiTypes> = Omit<T, StrapiAttributes> type MapRelation = { key: keyof ApiModelMap, many: boolean } // 关联类型可以是某个类型或MapRelation type ApiModelMap = { artist: { type: Artist relations: { portrait: StrapiMedia heroArtwork: {key: 'artworks', many: false} } } artworks: { type: Artwork[], relations: { figure: StrapiMedia } } exhibitions: { type: Exhibition[] } sites: { type: Site[] } navigation: { type: Navigation } footer: { type: Footer } } type StrapiApiTypes = { [P in keyof ApiModelMap]: ApiModelMap[P]['type'] } type ApiRelation = {key: ApiModelNames} type ApiModelNames = keyof ApiModelMap type ApiModel<T extends ApiModelNames> = UnpackArray<ApiModelMap[T]['type']> type ApiModelIsArray<T extends ApiModelNames> = ApiModelMap[T]['type'] extends [] ? true : never type GraphSubAttributesMap<N extends ApiModelNames> = ApiModelMap[N] extends { relations: unknown } ? ApiModelMap[N]['relations'] : never type GraphSubAttributeKeys<N extends ApiModelNames> = keyof GraphSubAttributesMap<N> type GetGraphRelationMap<N extends ApiModelNames, K extends GraphSubAttributeKeys<N>> = GraphSubAttributesMap<N>[K] extends ApiRelation ? GraphSubAttributesMap<GraphSubAttributesMap<N>[K]['key']> : never type GetGraphRelationType<N extends ApiModelNames, K extends GraphSubAttributeKeys<N>> = GraphSubAttributesMap<N>[K] extends ApiRelation ? UnpackArray<ApiModelMap[GraphSubAttributesMap<N>[K]['key']]['type']> : never
问题原因
TypeScript的分布式条件类型仅在泛型参数直接出现在extends子句的左侧时才会触发。当前写法中,GraphSubAttributesMap<N>[K] extends MapRelation并不是将K直接放在extends左侧,而是先计算GraphSubAttributesMap<N>[K]的结果(当K是联合类型时,这个结果是对应属性值的联合类型),再整体判断是否符合MapRelation。
以T3为例,K是'heroArtwork' | 'portrait',此时GraphSubAttributesMap<'artist'>[K]的结果是StrapiMedia | MapRelation。这个联合类型并不完全符合MapRelation(因为StrapiMedia不是MapRelation的子类型),所以条件判断直接返回false,走'case 3'分支,而非逐个处理联合类型的成员再合并结果。
解决方法
要让泛型对K的联合类型进行自动分发,需要让K直接出现在extends子句的左侧,触发分布式条件类型。可以通过两种方式修改:
方式一:直接在原类型中添加K的分布式判断
在原类型的条件分支最外层,先判断K是否属于目标键类型,以此触发分布式处理:
type GraphSubAttributeNames<N extends ApiModelNames, K extends GraphSubAttributeKeys<N>> = GraphSubAttributesMap<N> extends never ? 'never' : K extends keyof GraphSubAttributesMap<N> ? // 触发分布式条件类型 GraphSubAttributesMap<N>[K] extends MapRelation ? GetGraphRelationMap<N, K> extends never ? 'case 1' : 'case 2' : 'case 3' : never;
方式二:拆分单一成员处理逻辑
将针对单个K成员的判断逻辑抽成单独的泛型类型,外层泛型自动对联合类型的每个成员进行分发:
// 处理单个K成员的逻辑 type GraphSubAttributeNameSingle<N extends ApiModelNames, K extends GraphSubAttributeKeys<N>> = GraphSubAttributesMap<N> extends never ? 'never' : GraphSubAttributesMap<N>[K] extends MapRelation ? GetGraphRelationMap<N, K> extends never ? 'case 1' : 'case 2' : 'case 3'; // 外层泛型对K的联合类型自动分发 type GraphSubAttributeNames<N extends ApiModelNames, K extends GraphSubAttributeKeys<N>> = K extends infer U ? GraphSubAttributeNameSingle<N, U> : never;
修改后,T3的类型会正确返回'case 2' | 'case 3',符合预期。
内容的提问来源于stack exchange,提问作者Buntel

