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

TypeScript中如何合并联合类型里的泛型参数?

问题解答

为什么TypeScript不会自动合并为Result<T, string>?

TypeScript的返回类型推断逻辑是收集所有代码分支的精确返回类型,再组合成联合类型,它不会自动对联合中的泛型类型实例做参数合并。

具体来说:

  • Result.Ok(42)返回的是精确类型Result<number, never>,Result.Err('error')返回的是Result<never, string>
  • TypeScript会直接将这两个类型组合成联合类型Result<number, never> | Result<never, string>
  • 虽然never在简单类型合并中会被忽略(比如number | never会缩减为number),但TypeScript没有内置规则将联合的泛型实例(如Result<A, B> | Result<C, D>)自动合并为单一泛型实例Result<A | C, B | D>——因为这可能不符合开发者的预期(比如有些场景下需要保留泛型实例的联合,而非合并)。

如何无需显式标注函数返回类型实现合并效果?

可以通过调整Result类的静态方法逻辑,或者配合类型工具实现,以下是两种可行方案:

方案1:放宽Result静态方法的泛型约束

修改Ok和Err的返回类型签名,让泛型参数支持更宽的类型推断,促使TypeScript自动合并泛型参数:

class Result<T, E> {
  readonly #ok: boolean;
  readonly #inner: T | E;

  private constructor(ok: boolean, inner: T | E) {
    this.#ok = ok;
    this.#inner = inner;
  }

  // Ok返回Result<T, E>,E默认never,但允许被推断为更宽类型
  static Ok<T, E = never>(value: T): Result<T, E> {
    return new Result<T, E>(true, value);
  }

  // Err返回Result<T, E>,T默认never,但允许被推断为更宽类型
  static Err<E, T = never>(error: E): Result<T, E> {
    return new Result<T, E>(false, error);
  }
}

function mayThrow(success: boolean) {
  try {
    if (success) {
      // 显式指定泛型参数,帮助TypeScript合并类型
      return Result.Ok<number, string>(42);
    } else {
      throw 'error';
    }
  } catch {
    return Result.Err<string, number>('error');
  }
}

const res = mayThrow(true);
// 推断类型:const res: Result<number, string>

方案2:用类型工具合并联合类型,配合内部断言

定义类型工具来合并联合的Result类型,在函数内部通过断言告知TypeScript合并后的类型:

class Result<out T, out E> {
  readonly #ok: boolean;
  readonly #inner: T | E;

  private constructor(ok: boolean, inner: T | E) {
    this.#ok = ok;
    this.#inner = inner;
  }

  static Ok<T>(value: T): Result<T, never> {
    return new Result<T, never>(true, value);
  }

  static Err<E>(error: E): Result<never, E> {
    return new Result<never, E>(false, error);
  }
}

// 定义合并Result联合类型的工具类型
type MergeResult<U> = U extends Result<infer T, infer E> 
  ? Result<Exclude<T, never>, Exclude<E, never>> 
  : never;

function mayThrow(success: boolean) {
  const rawResult = (() => {
    try {
      if (success) {
        return Result.Ok(42);
      } else {
        throw 'error';
      }
    } catch {
      return Result.Err('error');
    }
  })();
  // 用断言让TypeScript识别合并后的类型
  return rawResult as MergeResult<typeof rawResult>;
}

const res = mayThrow(true);
// 推断类型:const res: Result<number, string>

这两种方案都不需要在函数签名上显式标注Result<number, string>,而是通过类型推断或工具类型实现预期的返回类型。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 20:56:04