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

如何在TypeScript中实现穷尽类型守卫并兼顾公共接口?

同时实现Result类型的类型守卫与公共接口约束

现有方案的矛盾

方案1:基于继承的Result接口

这种定义通过让Ok和Err继承Result接口,强制约束两者必须实现isOk()和isErr()方法,结构规范清晰:

interface Result<T, E> {
  isOk(): this is Ok<T>;
  isErr(): this is Err<E>;
}

interface Ok<T> extends Result<T, never> {
  value: T;
  isOk(): true;
  isErr(): false;
}

interface Err<E> extends Result<never, E> {
  error: E;
  isOk(): false;
  isErr(): true;
}

但缺点很明显:当用isOk()做类型检查后,未进入分支的代码里,result的类型依然是Result<T,E>,无法自动缩小为Err<E>,类型守卫没起到完全的作用。

方案2:联合类型定义Result

把Result直接定义为Ok和Err的联合类型,解决了类型缩小的问题:

interface Ok<T> {
  value: T;
  isOk(): true;
}

interface Err<E> {
  error: E;
  isOk(): false;
}

type Result<T, E> = Ok<T> | Err<E>;

现在执行if (result.isOk())检查后,分支外的result会自动被推断为Err<E>。但代价是Ok和Err没有公共的接口约束,无法强制要求两者必须实现统一的方法,也不能通过继承关系判断它们属于Result体系。

兼顾两者的解决方案

可以通过让Result同时作为接口和联合类型的方式,把两种方案的优势结合起来:

// 定义公共接口,约束Ok/Err必须实现的方法
interface Result<T, E> {
  isOk(): this is Ok<T>;
  isErr(): this is Err<E>;
}

// Ok继承Result接口,保证结构规范
interface Ok<T> extends Result<T, never> {
  value: T;
  isOk(): true;
  isErr(): false;
}

// Err同样继承Result接口
interface Err<E> extends Result<never, E> {
  error: E;
  isOk(): false;
  isErr(): true;
}

// 同时把Result定义为Ok和Err的联合类型,实现类型缩小
type Result<T, E> = Ok<T> | Err<E>;

为什么这个方案可行?

TypeScript允许同一个标识符既作为接口存在,又作为类型别名存在(只要两者的定义不冲突)。这里:

  • 接口Result负责约束Ok和Err必须实现isOk()、isErr()方法,保证了结构一致性;
  • 类型别名Result<T,E>作为Ok<T>|Err<E>的联合类型,让TypeScript能够在类型守卫后正确缩小类型范围。

现在再执行类型检查:

function handleResult<T, E>(result: Result<T, E>) {
  if (result.isOk()) {
    // 这里result被推断为Ok<T>,可以直接访问value
    console.log(result.value);
  } else {
    // 这里result自动被推断为Err<E>,可以访问error
    console.log(result.error);
  }
}

不管是if分支内还是分支外,类型都能正确缩小,同时Ok和Err必须遵守Result接口的约束,完美兼顾了两种方案的优势。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 03:52:29