TypeScript:如何用简洁写法实现多值判断并保留类型窄化
核心原理
要让if判断触发TypeScript的自动类型收窄,判断逻辑必须是返回类型谓词的类型守卫。原生Array.prototype.includes的类型定义仅返回普通布尔值,不满足类型守卫的要求;同时普通数组会被推断为宽的元素类型(比如string[]而非字面量联合类型),因此无法自动收窄传入值的类型,也就达不到串联多个||的推断效果。
实现方案
1. 通用类型守卫实现
首先写一个可复用的判断函数,兼容所有只读字面量数组的判断场景:
function isOneOf<T extends readonly unknown[]>( candidateArr: T, targetVal: unknown ): targetVal is T[number] { // 内部逻辑直接复用原生includes,无额外运行时开销 return (candidateArr as readonly unknown[]).includes(targetVal); }
这个函数的返回值是标准的类型谓词targetVal is T[number],完全符合TypeScript的类型收窄规则。使用时给候选值数组加上as const,让TS把数组推断为精确的只读字面量元组,即可获得和串联||完全一致的推断效果:
// 加as const后,数组的类型为readonly ["a", "b"] const allowedValues = ["a", "b"] as const; const str: string = getDynamicStr(); if (isOneOf(allowedValues, str)) { // 此处str的类型自动收窄为"a" | "b",和串联||的效果没有差异 }
2. 适配业务场景的优化
针对需要判断接口布尔类型属性名的场景,可以搭配内置映射类型的工具逻辑,自动从接口中提取符合要求的属性名,减少手动维护候选列表的疏漏:
interface Filters { isPromo?: boolean, isFreeShipping?: boolean, isNew?: boolean, isInStock?: boolean, title?: string } // 工具类型:提取接口中所有值类型为boolean的属性名 type BooleanPropKeys<T> = { [K in keyof T]-?: T[K] extends boolean ? K : never }[keyof T]; // 自动推导的类型为 "isPromo" | "isFreeShipping" | "isNew" | "isInStock" type FilterBooleanKey = BooleanPropKeys<Filters>; // 候选值数组:TS会自动约束数组元素必须是上述合法的布尔属性名,写错直接触发编译报错 const booleanFilterKeys: FilterBooleanKey[] = [ "isPromo", "isFreeShipping", "isNew", "isInStock" ]; let filters: Filters = {}; for (const filterValuePair of location.search.split("&")) { const [urlFilter, value] = filterValuePair.split("="); // 调用自定义的isOneOf做判断,类型收窄效果和串联多个||完全一致 if (isOneOf(booleanFilterKeys, urlFilter)) { // 此处urlFilter的类型自动收窄为四个布尔属性名的联合类型 // 给filters赋值时不会出现类型不匹配的编译错误 filters[urlFilter] = Boolean(value); } }
注意事项
- 候选数组如果不加
as const声明,TS会把数组元素推断为宽的string类型,最终判断时无法得到精确的字面量类型 - 自定义的
isOneOf函数已做只读数组兼容,不需要对用as const生成的元组做额外类型转换 - 整个方案的运行时判断逻辑和原生
includes完全一致,没有额外性能损耗
内容的提问来源于stack exchange,提问作者santiago arizti
相关产品推荐
相关产品推荐

