Skip to content

泛型基础

泛型是类型层的函数。理解这一点,后面所有内容都会顺。

泛型 = 类型的函数

ts
type 
Wrap
<
T
> = {
value
:
T
}
type
A
=
Wrap
<string>
type
B
=
Wrap
<number[]>

对照一下:

值层类型层
function wrap(x) { return { value: x } }type Wrap<T> = { value: T }
wrap('a')Wrap<string>
参数是 x参数是 T

约束:extends 的第一个含义

ts
interface HasLength {
  
length
: number
} function
logLen
<
T
extends HasLength>(
x
:
T
): number {
return
x
.
length
}
logLen
('abc')
logLen
([1, 2, 3])

T extends HasLength约束T 必须满足 HasLength

注意 extends 的三个含义别搞混:

ts
// ① 约束(在泛型参数位置)
type 
F1
<
T
extends string> =
T
// ② 判断(在条件类型位置) type
F2
<
T
> =
T
extends string ? true : false
// ③ 继承(在 interface 位置) interface A {
x
: number
} interface B extends A {
y
: number
}

默认值

ts
type 
EventMap
<
T
extends
Record
<string, unknown> =
Record
<string, unknown>> = {
[
K
in keyof
T
]: (
payload
:
T
[
K
]) => void
}

递归类型里,默认参数常被用作累加器(类型层没有可变变量,只能靠参数往下传):

ts
type 
BuildTuple
<
N
extends number,
Acc
extends unknown[] = []
> =
Acc
['length'] extends
N
?
Acc
:
BuildTuple
<
N
, [...
Acc
, unknown]>
type
T3
=
BuildTuple
<3>
type
T0
=
BuildTuple
<0>

泛型推导

大部分时候不用手动传参,编译器会从参数推出来:

ts
declare function 
identity
<
T
>(
x
:
T
):
T
const
a
=
identity
('hello')
type
T
= typeof
a
declare function
pick
<
T
,
K
extends keyof
T
>(
obj
:
T
,
keys
:
K
[]):
Pick
<
T
,
K
>
const
r
=
pick
({
a
: 1,
b
: 'x',
c
: true }, ['a', 'b'])
type
R
= typeof
r

推导不出来时显式指定

ts
declare function 
makePair
<
T
>(): [
T
,
T
]
const
p
=
makePair
<string>()
type
T
= typeof
p

部分指定(TS 没有这功能)

TS 不支持"只指定一部分类型参数",要么全推要么全写。绕过的常见做法是用柯里化

ts
declare function 
create
<
T
>(): <
U
>(
input
:
U
) => [
T
,
U
]
const
f
=
create
<string>()
const
r
=
f
(123)
type
R
= typeof
r

泛型在函数 vs 类型别名 vs 接口

ts
// 函数
function 
f
<
T
>(
x
:
T
):
T
{
return
x
} // 类型别名 type
Alias
<
T
> = {
value
:
T
}
// 接口 interface
Iface
<
T
> {
value
:
T
} // 类 class
Box
<
T
> {
constructor(public
value
:
T
) {}
}

常见模式

关联两个参数

ts
declare function 
getKey
<
T
,
K
extends keyof
T
>(
obj
:
T
,
key
:
K
):
T
[
K
]
const
obj
= {
a
: 1,
b
: 'x' }
const
v1
=
getKey
(
obj
, 'a')
type
A
= typeof
v1
const
v2
=
getKey
(
obj
, 'b')
type
B
= typeof
v2

返回值类型跟着 key 变——这就是泛型真正的价值:让类型之间产生关联,而不是各自独立。

泛型约束实现接口

ts
interface 
Repository
<
T
> {
find
(
id
: string):
Promise
<
T
| undefined>
save
(
entity
:
T
):
Promise
<void>
} // 实现一个具体的 Repository class
UserRepo
implements
Repository
<{
id
: string;
name
: string }> {
async
find
(
id
: string) {
return {
id
,
name
: 'x' }
} async
save
(
entity
: {
id
: string;
name
: string }) {}
}

条件返回类型

ts
type 
Unwrap
<
T
> =
T
extends
Promise
<infer
U
> ?
U
:
T
declare function
maybeAwait
<
T
>(
x
:
T
):
Unwrap
<
T
>
declare const
p
:
Promise
<number>
const
a
=
maybeAwait
(
p
)
type
A
= typeof
a
declare const
n
: number
const
b
=
maybeAwait
(
n
)
type
B
= typeof
b

const 类型参数(TS 5.0)

ts
declare function 
f1
<
T
>(
x
:
T
[]):
T
[]
declare function
f2
<const
T
>(
x
:
T
[]):
T
[]
const
a
=
f1
(['x', 'y'])
type
A
= typeof
a
const
b
=
f2
(['x', 'y'])
type
B
= typeof
b

const 让泛型推导走"字面量模式",调用方不用再写 as const。写 SDK 时非常有用。

NoInfer(TS 5.4)

阻止某个类型参数参与推断,只用于校验:

ts
declare function 
f1
<
T
>(
items
:
T
[],
init
:
T
):
T
declare function
f2
<
T
>(
items
:
T
[],
init
:
NoInfer
<
T
>):
T
const
a
=
f1
(['x', 'y'], 'z')
type
A
= typeof
a
const
b
=
f2
(['x', 'y'], 'z')
// 报错:'z' 不在 'x' | 'y' 里

典型场景:有默认值或初始值的 API,不希望初始值把类型推宽。

下一步

基于 VitePress 与 Twoslash 构建