本人首发于五块木头个人博客
泛型
首先, 我们来写一个函数: loggerNum 函数, 这个函数的作用是 console.log 输入数字值, 然后将该值返回:
const loggerNum = (params: number) => {
console.log(params);
return params;
};
loggerNum(1); // number
loggerNum('1'); // Error: 字符串不能分配给数字类型
假如说我们还需要打印字符串呢?
const loggerStr = (params: string) => {
console.log(params);
return params;
};
loggerStr('1'); // string
假如说我们还有布尔类型, 害, 是不是得继续声明一个函数来实现, 算了, 我使用 any:
const logger = (params: any) => {
console.log(params);
return params;
};
// logger(1); // any
// logger('1'); // any
// logger(false); // any
这种方式当然没有问题, 但是它失去了原有的类型以及类型检查(不到万不得已, 请不要使用 any), 所以我们需要一种捕捉参数类型的方式, 以便我们可以使用它来表示返回的内容.
function logger<Type>(params: Type): Type {
console.log(params);
return params;
}
我们现在给logger
函数添加来一个类型变量Type
, 然后Type
可以捕获到提供的类型(比如说number
、string
…), 后面我们就可以使用该Type
, 在这里我们使用Type
作为参数和返回值, 所以 Ts 会检查其返回值是不是与Type
是一致的.
logger<number>(1); // number
logger<string>('1'); // string
logger<boolean>(true); // boolean
logger<number[]>([1, 2, 3]); // number[]
logger<string>(1); // 数字类型不能分配给字符串类型
我们也可以使用类型参数自动推断的方式, ts 编译器会根据我们传入的参数自动设置其类型.
logger(1); // number
logger('1'); // string
logger(true); // boolean
logger([1, 2, 3]); // number[]
我们第一个简单的泛型函数就实现啦~.
泛型约束
我们再拿上面那个例子来说, 我们想打印一下参数的长度, 改一下代码:
function logger<Type>(params: Type): Type {
console.log(params.length); // Property 'length' does not exist on type 'Type'.
return params;
}
报错了, 别慌张, 因为使用泛型, 我们此时不能访问它的任何属性, 所以这个时候报错是正常的, 做一个类比, 我们把之前的例子比喻成一个充电器, 什么充电器都可以, 只要是充电器就好了, 现在充电器加了约束(类型约束), 只能适配 type-c 充电器, 不是 type-c 充电器都不行.
这里也是一样的, 我们希望参数把类型做一个限制, 至少具有length
属性的类型才可以传入, 所以来看看代码.
function logger<Type extends {
length: number }>(params: Type): Type {
console.log(params.length);
return params;
}
Type extends {length:number}
就是做了一个类型约束, 只有具有length
属性的对象才可以传入.
logger({
length: 0 });
logger([1, 2, 3]);
logger(1); // number 不能分配给 { length:number }
在泛型约束中使用类型参数
有了上面的基础, 我们再来实现一个方法getProperty(obj, key)
返回对象中的指定 key 的 value.
function getProperty<Type>(obj: Type, key: keyof Type) {
return obj[key];
}
keyof 后面会介绍
默认类型参数
在 Js 中有默认参数, 如果没有传值时使用该默认值.
const inc = (count, step = 1) => count + step;
inc(1); // 2
inc(1, 5); // 6
而在 Ts 中有默认类型参数, 如果没有传入参数就使用默认类型参数, 上面getProperty
的例子更新一下:
function getProperty<Type, Key = keyof Type>(obj: Type, key: Key) {
return obj[key]; // Key 不能当作Type的索引
}
成功的报错了, Key
在不传入类型的时候,才会是默认的keyof Type
, 而如果Key
如果传入了number
、string
等类型时, Key
就会采用传入的类型:
getProperty({
name: 'senlin', age: 18 }, false); // getProperty(obj: Person, key: boolean)
getProperty({
name: 'senlin', age: 18 }, '444'); // getProperty(obj: Person, key: string)
所以我们需要对Key
做一个参数类型约束:
function getProperty<Type, Key extends keyof Type = keyof Type>(
obj: Type,
key: Key
) {
return obj[key];
}
getProperty({
name: 'senlin', age: 18 }, false); // Error: false不能分配给'name'|'age'
getProperty({
name: 'senlin', age: 18 }, '444'); // Error: '444'不能分配给'name'|'age'
getProperty({
name: 'senlin', age: 18 }, 'name'); // OK: 'senlin'
交叉类型 &
类型运算符 & 用于创建交叉类型:
type A = 'a' | 'b' | 'c';
type B = 'b' | 'c' | 'd';
// "b" | "c"
type Intersection = A & B;
如果我们将类型 A 和类型 B 视为集合, 那么 A & B 就是两个集合的交集, 换句来说: 结果的成员是两个操作数的成员.
与 never、unknown 的爱恨情仇
type A = 'a' | 'b';
type D = A & never; // never
type E = A & unknown; // 'a' | 'b'
如果把 ts 的类型当作一个集合来看的话, unknown 相当于集合中的全集, 它是一个顶部类型:
- 空集(never)和其他集合(A)做交集(交叉类型) = 空集(never).
- 全集(unknown)和其他集合(A)做交集(交叉类型) = 其他类型.
联合类型 |
我们有这样子的一个函数, 接受一个参数, 如果是数组, 则原样返回, 如果不是数组, 将值包裹成数组.
function wrapToArray(params: number) {
return [params];
}
wrapToArray(1); // number[]
wrapToArray([1]); // number[] 不能分配给number
所以这个时候, 我们就需要采用联合类型, 期望参数可以传入数字和数字数组.
function wrapToArray(params: number | number[]) {
// 类型缩小
if (Array.isArray(params)) return params;
return [params];
}
wrapToArray(1); // number[]
wrapToArray([1]); // number[]
注意这里的参数number|number[]
, 意思就是允许传入数字和数字数组类型, 这里我们使用Array.isArray
来进行类型缩小, 如果是数组就直接返回, 如果不是数组就进行包裹一层返回.
为什么要进行类型缩小? 因为 Ts 在使用过程中需要明确具体的类型(any 除外!), 当前类型是
number|number[]
, 并不清楚是number
类型还是number[]
类型, 所以需要使用Array.isArray
来将其缩小到number[]
类型.
我们也可以使用|
来创建联合类型:
type A = 'a' | 'b' | 'c';
type B = 'b' | 'c' | 'd';
// "a" | "b" | "c" | "d"
type Union = A | B;
如果把 A 和 B 当作两个集合, 联合类型就是求两个集合的并集, 结果的成员是至少一个操作数的成员.
如果我们对一个对象类型进行keyof
操作的时候, 也会得到联合类型:
type Person = {
name: string;
age: number;
};
type PersonKeys = keyof Person; // 'name' | 'age'
对象类型的联合
由于联合类型的每个成员都是至少一个组件类型的成员, 我们只能安全的访问所有组件类型共享的属性(A 行). 如果要访问其他属性, 我们需要一个类型保护(B 行):
type