TypeScript如何基于单属性测试实现通用联合类型收窄,避免重复编写类型守卫
通用联合类型分支收窄的类型守卫实现
现有实现的问题点
你当前手动编写类型守卫、以及初步尝试的通用函数雏形,存在三个可优化的核心问题:
- 手写每个分支的存在性判断逻辑重复度极高,大量同类分支需要重复编写结构完全一致的return校验代码
- 初步写的
makeNarrower把属性参数定义为宽泛的string类型,没有和目标收窄类型做类型绑定,传入拼写错误的属性名时TypeScript编译期不会拦截,只会在运行时暴露收窄逻辑完全失效的问题 - 手动判断
分支属性 !== undefined的逻辑本身存在漏洞:如果专属属性的类型本身允许值为undefined,哪怕属性键真实存在,判断逻辑也会返回错误结果
可复用的通用守卫生成实现
通过泛型约束把属性名和目标分支的专属属性做强绑定,一次性生成所有分支的类型守卫,消除重复编码:
// 分布式Omit工具:处理联合类型的属性剔除,避免直接对联合类型做Omit导致的分支丢失 type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K> : never; function makeNarrower< // 待收窄的源联合类型 SourceUnion, // 目标收窄到的具体分支类型 TargetBranch extends SourceUnion, // 目标分支的专属属性键,通过泛型约束做类型关联 UniqueKey extends keyof TargetBranch >(property: UniqueKey) { // 运行时直接通过in操作符校验属性存在性,比值判断逻辑更严谨 return (input: SourceUnion): input is TargetBranch => { return property in input; }; }
适配现有代码的使用方式
基于上述通用函数,你不需要为每个分支单独手写守卫逻辑,只需要一行调用即可生成对应守卫:
export type ZeroTurn = { zeroturn: string } export type OneTurn = { oneturn: string } export type Turn = ZeroTurn | OneTurn // 生成ZeroTurn类型守卫 export const isZeroTurn = makeNarrower<Turn, ZeroTurn, 'zeroturn'>('zeroturn'); // 生成OneTurn类型守卫 export const isOneTurn = makeNarrower<Turn, OneTurn, 'oneturn'>('oneturn');
实现收益
- 全量复用收窄校验逻辑,所有分支的类型守卫生成仅需单行调用,完全消除重复代码
- 编译期强校验:传入的属性名必须是目标分支的合法键名,属性名拼写错误、参数传错类型的问题会直接在编码阶段被拦截
- 运行时判断可靠性更高:通过*
in操作符*判断属性键的存在性,不会因为属性值为undefined产生误判,比手动判断属性值是否存在的逻辑覆盖场景更全
内容的提问来源于stack exchange,提问作者eguneys
相关产品推荐
相关产品推荐

