泛型
泛型约束
在泛型中,我们可以使用 extends 关键字来约束传入的泛型参数必须符合要求。关于 extends
,这里我们暂时只需要了解非常简单的判断逻辑,即 A extends B
意味着 A 是 B 的子类型,表现在 A 比 B 的类型更复杂,或者说 B 中的属性在 A 中一定都有,反之则不一定。具体来说,可以分为以下几类。
- 字面量类型是对应原始类型的子类型,即
'aaa' extends string
,599 extends number
成立 - 联合类型子集均为联合类型的子类型,即
1
、1 | 2 | 3
是1 | 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>>> {}