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
相关产品推荐
相关产品推荐

