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

TypeScript:字面量类型时必填对应属性,否则全设为可选

为Spotify API搜索端点实现动态类型收缩

我正在给Spotify API的搜索端点定义TypeScript类型,目前的基础类型定义如下:

export enum ResourceType {
  Album = 'album',
  Artist = 'artist',
  Playlist = 'playlist',
  Track = 'track',
  Show = 'show',
  Episode = 'episode',
  Audiobook = 'audiobook',

  Albums = 'albums',
  Artists = 'artists',
  Playlists = 'playlists',
  Tracks = 'tracks',
  Shows = 'shows',
  Episodes = 'episodes',
  Audiobooks = 'audiobooks',
}

interface ResourceTypeToResultKey {
  [ResourceType.Album]: ResourceType.Albums
  [ResourceType.Artist]: ResourceType.Artists
  [ResourceType.Track]: ResourceType.Tracks
  [ResourceType.Playlist]: ResourceType.Playlists
  [ResourceType.Show]: ResourceType.Shows
  [ResourceType.Episode]: ResourceType.Episodes
  [ResourceType.Audiobook]: ResourceType.Audiobooks
}

type SearchType = 
  | ResourceType.Album 
  | ResourceType.Artist 
  | ResourceType.Playlist 
  | ResourceType.Track 
  | ResourceType.Show 
  | ResourceType.Episode 
  | ResourceType.Audiobook;

// 所有属性均为可选的完整结果类型
type SearchResults = {
  [K in SearchType as ResourceTypeToResultKey[K]]?: unknown
}

根据API文档,响应的规则是仅返回你请求的键:

  • 当传入字面量类型数组时,应该缩小类型范围,将对应属性设为必填
  • 当传入非字面量类型(比如SearchType[])时,无法确定具体返回内容,应该返回所有属性均为可选的完整类型

我期望实现的效果如下:

// 期望的函数类型定义
declare const search: <T extends SearchType>(types: T[]) => /* 动态返回类型 */;

// 情况1:传入非字面量类型的数组
declare const types: SearchType[];
const result = search(types);
// result类型应为SearchResults(所有属性可选)
result.tracks; // 应为unknown | undefined

// 情况2:传入字面量类型数组
const result2 = search([ResourceType.Track]);
// result2类型应为{ tracks: unknown }
result2.tracks; // 应为unknown(必填)

请问这种需求是否可行?


可行的实现方案

通过TypeScript的条件类型可以实现这个需求,核心思路是判断泛型T是否为未窄化的SearchType,以此返回不同的结果类型:

type SearchResult<T extends SearchType> = 
  // 如果T是完整的SearchType(未被窄化),返回全可选类型
  [T] extends [SearchType] ? SearchResults : 
  // 否则返回对应属性必填的类型
  Required<Pick<SearchResults, ResourceTypeToResultKey[T]>>;

declare const search: <T extends SearchType>(types: T[]) => SearchResult<T>;

验证效果:

declare const types: SearchType[];
const result = search(types);
// result类型为SearchResults,所有属性均为可选
result.tracks; // 类型:unknown | undefined

const result2 = search([ResourceType.Track]);
// result2类型为{ tracks: unknown }
result2.tracks; // 类型:unknown(必填,无undefined)

const result3 = search([ResourceType.Album, ResourceType.Artist]);
// result3类型为{ albums: unknown; artists: unknown }
result3.albums; // 必填
result3.artists; // 必填

原理说明

使用[T] extends [SearchType]而不是直接T extends SearchType,是为了避免分布式条件类型的影响。当T是联合类型时,直接的T extends SearchType会触发分布式逻辑,而包裹成元组后可以将T作为整体判断是否等于完整的SearchType,从而区分“未窄化的完整类型”和“已窄化的字面量/联合类型”。

内容的提问来源于stack exchange,提问作者nook

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 18:24:57