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

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 ✅

关键修改说明

  1. 函数重载区分场景:用两个重载签名明确两种调用模式,避免单一泛型导致的类型合并问题:
    • 传入set时,强制defaultValue必须是集合中的有效值,返回类型与集合值一致
    • 未传入set时,直接返回通用string类型,不再绑定defaultValue的具体字面量类型
  2. 简化类型逻辑:实现部分不再使用复杂的条件类型,依赖重载签名提供的类型约束,解决了原代码中return value的类型不匹配错误
  3. 强制defaultValue合法性:当传入set时,TypeScript会自动校验defaultValue是否属于集合范围,不符合时直接抛出类型错误,完全符合预期需求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 01:53:11