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

TypeScript中如何判断函数多类型参数的实际传入类型?

这确实是TypeScript处理联合类型时很常见的困惑——既要准确收窄类型,又不想写一堆冗余代码。咱们一步步来拆解最合适的方案:

优先利用类型本身的结构差异做类型守卫

你定义的A/B/C/D其实本身就有明显的结构差异,完全可以用TypeScript内置的类型守卫语法(typeof、in操作符)来直接判断,不需要额外给类型加type/kind属性,也不用写自定义守卫函数。

比如你的例子中:

  • A的value是字符串类型,可以用typeof parameter.value === 'string'判断
  • B的value是数字类型,用typeof parameter.value === 'number'判断
  • C的value有title属性,用'title' in parameter.value判断
  • D的value有subtitle属性,用'subtitle' in parameter.value判断

修改后的函数代码完全合法,而且TS会自动帮你收窄类型:

interface SomeCustomInterface { title: string }
interface OtherCustomInterface { subtitle: string }
interface A { value: string }
interface B { value: number }
interface C { value: SomeCustomInterface }
interface D { value: OtherCustomInterface }

function doSomething(parameter: A | B | C | D): string {
    if (typeof parameter.value === 'string') {
        // TS知道这里parameter是A类型
        return parameter.value;
    } else if (typeof parameter.value === 'number') {
        // TS知道这里parameter是B类型
        return parameter.value.toString();
    } else if ('title' in parameter.value) {
        // TS知道这里parameter是C类型
        return parameter.value.title;
    } else {
        // TS知道这里parameter是D类型(因为前面已经排除了其他三种)
        return parameter.value.subtitle;
    }
}

这种方式几乎没有额外开销,完全依赖类型本身的结构,代码也简洁。

结构差异不明显时用可辨识联合类型(减少冗余版)

如果你的类型之间结构差异很小,比如两个接口都有value但都是对象,只是内部属性不同,这时候可辨识联合类型(给每个类型加一个唯一的标识属性)确实是更清晰的方案,但可以通过交叉类型来减少冗余代码:

// 定义一个基础的带类型标识的接口
type Typed<T extends string> = { type: T };

interface SomeCustomInterface { title: string }
interface OtherCustomInterface { subtitle: string }

// 用交叉类型复用基础标识,只定义差异部分
type A = Typed<'A'> & { value: string };
type B = Typed<'B'> & { value: number };
type C = Typed<'C'> & { value: SomeCustomInterface };
type D = Typed<'D'> & { value: OtherCustomInterface };

function doSomething(parameter: A | B | C | D): string {
    switch (parameter.type) {
        case 'A':
            return parameter.value;
        case 'B':
            return parameter.value.toString();
        case 'C':
            return parameter.value.title;
        case 'D':
            return parameter.value.subtitle;
        default:
            // 这里可以加一个never类型的断言,确保覆盖所有情况
            const _exhaustiveCheck: never = parameter;
            throw new Error(`Unexpected type: ${_exhaustiveCheck}`);
    }
}

这种方式只需要写一次基础的Typed类型,后续的类型定义只需要关注差异,冗余度很低,而且switch的写法非常直观,TS也能完美收窄类型。

自定义类型守卫的优化思路(可选)

如果确实需要自定义类型守卫(比如判断逻辑复杂),也可以通过复用逻辑减少重复代码。比如针对你的例子,可以写一个通用的守卫函数:

function isTypeWithValueProp<T>(obj: unknown, prop: keyof T): obj is { value: T } {
    return typeof obj === 'object' && obj !== null && 'value' in obj && prop in (obj as { value: unknown }).value;
}

// 使用时
function doSomething(parameter: A | B | C | D): string {
    if (typeof parameter.value === 'string') {
        return parameter.value;
    } else if (typeof parameter.value === 'number') {
        return parameter.value.toString();
    } else if (isTypeWithValueProp<SomeCustomInterface>(parameter, 'title')) {
        return parameter.value.title;
    } else {
        return parameter.value.subtitle;
    }
}

不过这种方式只适合逻辑复杂的场景,大多数情况下前面两种方案已经足够。

总结一下:优先用类型本身的结构差异做判断,零冗余;如果结构差异不明显,用带交叉类型的可辨识联合,尽量减少重复代码;自定义守卫作为最后选项。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 12:57:43