TypeScript如何从联合类型中提取匹配指定属性值的子类型
问题描述
我定义了如下联合类型 PromptOptions:
type PromptOptions = | BasePromptOptions | BooleanPromptOptions | StringPromptOptions type BasePromptOptions = { kind: string | (() => string) }; type BooleanPromptOptions = { kind: 'confirm' }; type StringPromptOptions = { kind: 'input' | 'invisible' | 'list' | 'password' | 'text'; };
实现目标
给定任意类型,例如 type Lookup = { kind: 'invisible' },我希望定义泛型 ExtractedType<T>,实现 ExtractedType<Lookup> = StringPromptOptions 的匹配效果。
当前直接使用内置Extract实现的写法:type ExtractedType<T> = Extract<PromptOptions, T>,仅在传入的类型和某个Prompt成员类型完全匹配时能正常工作(比如ExtractedType<{ kind: 'confirm' }>能正确返回BooleanPromptOptions),但传入{ kind: 'invisible' }这类类型时,返回值为never,不符合预期的StringPromptOptions结果。
我需要实现基于kind字段的兼容性匹配逻辑,不确定具体类型写法,也不确定该需求是否可实现。
原因分析
内置Extract<T, U>的默认逻辑是从联合类型T中筛选出可以赋值给U的成员,本质实现为:
type Extract<T, U> = T extends U ? T : never
当传入U = { kind: 'invisible' }时,TS会逐个检查PromptOptions的联合成员是否满足P extends {kind: 'invisible'}:
BooleanPromptOptions的kind是'confirm',不满足,返回neverStringPromptOptions的kind是5个字符串字面量的联合,除了'invisible'还可能是'input'/'password'等其他值,无法满足kind必须恒为'invisible'的约束,不满足条件,返回neverBasePromptOptions的kind是宽类型string | (()=>string),同样不满足恒为'invisible'的约束,返回never
最终结果就是never,和预期不符。
你实际需要的匹配逻辑是:筛选出PromptOptions联合中,kind字段类型可以兼容传入类型T的kind值的最精确成员。
实现方案
方案1:简洁硬编码(适合类型固定的场景)
由于你的Prompt类型数量固定,直接按优先级从高到低(精确类型优先,兜底类型最后)判断即可,写法简单易维护:
type ExtractedType<T extends { kind: any }> = T['kind'] extends BooleanPromptOptions['kind'] ? BooleanPromptOptions : T['kind'] extends StringPromptOptions['kind'] ? StringPromptOptions : T['kind'] extends BasePromptOptions['kind'] ? BasePromptOptions : never;
验证效果
ExtractedType<{ kind: 'confirm' }>→BooleanPromptOptions,符合预期ExtractedType<{ kind: 'invisible' }>→StringPromptOptions,符合预期ExtractedType<{ kind: 'custom-string' }>→BasePromptOptions,符合预期ExtractedType<{ kind: () => 'test' }>→BasePromptOptions,符合预期
方案2:通用自动匹配(适合后续频繁扩展Prompt类型的场景)
如果后续会给PromptOptions联合新增更多精确类型,不想每次新增都修改判断逻辑,可以用分布式条件类型实现自动匹配,优先匹配kind为字面量类型的精确成员,匹配不到再回退到兜底的BasePromptOptions:
// 自动筛选联合中kind为精确字面量类型、且能兼容目标kind值的成员 type GetPrecisePrompt<TKind, P = PromptOptions> = P extends any ? P extends { kind: infer K } ? string extends K // 排除kind为宽string类型的兜底成员 ? never : TKind extends K ? P : never : never : never; type ExtractedType<T extends { kind: any }> = GetPrecisePrompt<T['kind']> extends infer PreciseMatched ? [PreciseMatched] extends [never] ? T['kind'] extends BasePromptOptions['kind'] ? BasePromptOptions : never : PreciseMatched : never;
后续新增精确Prompt类型(比如type NumberPromptOptions = {kind: 'number'}),只要把它加入PromptOptions联合,不需要修改ExtractedType逻辑即可自动完成匹配。
内容的提问来源于stack exchange,提问作者andyroo
相关产品推荐
相关产品推荐

