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
相关产品推荐
相关产品推荐

