TypeScript泛型部分只读设置及sanitize函数类型修正
问题描述
我编写了一个sanitizer函数,该函数可能接收一组可选的有效值集合:若接收该集合,则强制仅允许使用这些值;若未接收,则仅检查值是否为string类型。当任一检查失败时,返回defaultValue。目前我的代码如下:
function validateEnum<T extends string>( value: string, set: Set<T> ): value is T { return set.has(value as T); } function sanitize<S extends string = never>(value: any, defaultValue: S extends never ? string : S, set?: Set<S>): S extends never ? string : S { if (typeof value !== "string") { return defaultValue; } if (set && !validateEnum(value, set)) { return defaultValue; } return value; // This line throws an error "Type 'string' is not assignable to type 'S extends never ? string : S'" } const a = sanitize("abc", "a", new Set(["a", "b", "c"])); // should resolve to "a" | "b" | "c", this is correct const b = sanitize("abc", "b", new Set(["c"])); // should resolve to just "c", and "b" as defaultValue should throw a typescript error, it is now resolving to "b" | "c" const c = sanitize("abc", "c"); // should resolve to generic string, it is now resolving to "c"
当前存在以下问题:
return value;行抛出类型错误:Type 'string' is not assignable to type 'S extends never ? string : S'- 函数调用的类型推断不符合预期:
const b = sanitize("abc", "b", new Set(["c"])):预期返回类型为"c",且defaultValue传入"b"应触发TypeScript错误,但实际返回"b" | "c"const c = sanitize("abc", "c"):预期返回通用string类型,但实际返回"c"
TypeScript会将defaultValue的类型合并到泛型S中,请问如何让泛型S对defaultValue设为只读,同时正确分配返回值类型?
解决方案
通过函数重载拆分两种调用场景,明确约束类型关系,即可解决所有问题:
修正后的代码
function validateEnum<T extends string>( value: string, set: Set<T> ): value is T { return set.has(value as T); } // 重载1:传入有效值集合时,defaultValue必须属于该集合,返回集合值类型 function sanitize<T extends string>( value: any, defaultValue: T, set: Set<T> ): T; // 重载2:未传入有效值集合时,返回通用string类型 function sanitize( value: any, defaultValue: string ): string; // 函数实现(忽略重载的具体类型约束,只处理运行逻辑) function sanitize(value: any, defaultValue: string, set?: Set<string>): string { if (typeof value !== "string") { return defaultValue; } if (set && !validateEnum(value, set)) { return defaultValue; } return value; } // 测试验证 const a = sanitize("abc", "a", new Set(["a", "b", "c"])); // 类型为 "a" | "b" | "c" ✅ const b = sanitize("abc", "b", new Set(["c"])); // 触发类型错误:"b" 不属于 Set<"c"> ✅ const c = sanitize("abc", "c"); // 类型为 string ✅
关键修改说明
- 函数重载区分场景:用两个重载签名明确两种调用模式,避免单一泛型导致的类型合并问题:
- 传入
set时,强制defaultValue必须是集合中的有效值,返回类型与集合值一致 - 未传入
set时,直接返回通用string类型,不再绑定defaultValue的具体字面量类型
- 传入
- 简化类型逻辑:实现部分不再使用复杂的条件类型,依赖重载签名提供的类型约束,解决了原代码中
return value的类型不匹配错误 - 强制defaultValue合法性:当传入
set时,TypeScript会自动校验defaultValue是否属于集合范围,不符合时直接抛出类型错误,完全符合预期需求
内容的提问来源于stack exchange,提问作者Semanual
相关产品推荐
相关产品推荐

