You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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',不满足,返回never
  • StringPromptOptions的kind是5个字符串字面量的联合,除了'invisible'还可能是'input'/'password'等其他值,无法满足kind必须恒为'invisible'的约束,不满足条件,返回never
  • BasePromptOptions的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 11:48:27