泛型

泛型约束

在泛型中,我们可以使用 extends 关键字来约束传入的泛型参数必须符合要求。关于 extends ,这里我们暂时只需要了解非常简单的判断逻辑,即 A extends B 意味着 A 是 B 的子类型,表现在 A 比 B 的类型更复杂,或者说 B 中的属性在 A 中一定都有,反之则不一定。具体来说,可以分为以下几类。

  • 字面量类型是对应原始类型的子类型,即 'aaa' extends string599 extends number 成立
  • 联合类型子集均为联合类型的子类型,即 11 | 2 | 31 | 2 | 3 | 4 的子类型。
type ResStatus<ResCode extends number> = ResCode extends 10000 | 10001 | 10002
  ? 'success'
  : 'failure';

这个例子会根据传入的请求码判断请求是否成功,这意味着它只能处理数字字面量类型的参数,因此这里我们通过 extends number 来标明其类型约束,如果传入一个不合法的值,就会出现类型错误.

与此同时,如果我们想让这个类型别名可以无需显式传入泛型参数也能调用,并且默认情况下是成功地,这样就可以为这个泛型参数声明一个默认值

type ResStatus<ResCode extends number = 10000> = ResCode extends 10000 | 10001 | 10002
  ? 'success'
  : 'failure';

// "success"
type Res4 = ResStatus;

多泛型关联

我们不仅可以同时传入多个泛型参数,还可以让这几个泛型参数之间也存在联系。我们可以先看一个简单的场景,条件类型下的多泛型参数。

type Conditional<Type, Condition, TruthyResult, FalsyResult> =
  Type extends Condition ? TruthyResult : FalsyResult;

//  "passed!"
type Result1 = Conditional<'aaa', string, 'passed!', 'rejected!'>;

// "rejected!"
type Result2 = Conditional<'aaa', boolean, 'passed!', 'rejected!'>;

对象类型中的泛型

最常见的一个相应的例子应该是响应类型结构的泛型处理。

interface IRes<TData = unknown> {
  code: number;
  error?: string;
  data: TData;
}

这个接口描述了一个通用的响应类型结构,预留出了实际响应数据的泛型坑位,然后在你的请求函数中就可以传入特定的响应类型了。

interface IUserProfileRes {
  name: string;
  homepage: string;
  avatar: string;
}

function fetchUserProfile(): Promise<IRes<IUserProfileRes>> {}

type StatusSucceed = boolean;
function handleOperation(): Promise<IRes<StatusSucceed>> {}

而泛型嵌套的场景也非常常用,比如对存在分页结构的数据,我们也可以将其分页的响应结构抽离出来。

interface IPaginationRes<TItem = unknown> {
  data: TItem[];
  page: number;
  totalCount: number;
  hasNextPage: boolean;
}

function fetchUserProfileList(): Promise<IRes<IPaginationRes<IUserProfileRes>>> {}