8、TS中的泛型
多个位置的类型信息应该保持一直或有关联的信息。
泛型
泛型: 是指附属于函数、类、接口、类型别名之上的类型。
在函数中使用泛型
假如有这样一种场景:
有一个函数 getArrStart
可以获取数组的前 n
项数据
function getArrStart(arr, n) {
if (n > arr.length) {
return arr
}
const result: any = [];
for (let i = 0; i < n; i++) {
result.push(arr[i]);
}
return result;
}
这里的函数实现没有加上类型检查, 按照之前文章的知识, 加上类型检查会遇见问题:
- 参数 arr 的类型该是什么
根据函数的实现, 至少可以知道 arr 一定是一个 数组, 但是不确定是一个什么类型的数组, 有可能使用者传递一个 number、string、object、any 的数组, 也就是参数 arr 是根据使用者传递的类型来决定的。 而且返回结果的 数组 类型也是根据 arr 的类型来确定, 函数返回值类型也是根据 arr 类型确定的, 这就给我一种感觉, 好像是缺少了某一块信息, 这个信息贯通函数的每一个角落, 仿佛需要有人告诉我这个丢失的信息 泛型 就是处理这一类的问题。
使用泛型解决弥补缺失的信息
在函数名后使用 <泛型名称>
来添加一个 泛型
function getArrStart<T>(arr: T[], n): T[] {
if (n > arr.length) {
return arr
}
const result: T[] = [];
for (let i = 0; i < n; i++) {
result.push(arr[i]);
}
return result;
}
使用
const result = getArrStart<number>([1, 2, 3, 4, 5], 3);
在函数调用时在函数名称后加上 <类型>
相当于给上面的 T 赋值的感觉。
泛型还可以赋值默认值
function getArrStart<T = number>(arr: T[], n): T[] {
if (n > arr.length) {
return arr
}
const result: T[] = [];
for (let i = 0; i < n; i++) {
result.push(arr[i]);
}
return result;
}
在使用时如果没有传递泛型类型,则使用默认类型 number
这就是 泛型 在函数上的使用, 但函数内一个类型需要根据传递的参数来决定时, 就可以使用泛型。
在类型别名、接口、类中使用泛型
直接在名称后写上 <泛型名称>
类型接口
type Condition<T> = (item: T[], index: T[]) => boolean;
写一个 filter
使用一下
type Condition<T> = (item: T[], index: number) => boolean;
function filter<T>(arr: T[], callback: Condition<T>): T[] {
const result: T[] = []
for (let i = 0; i < arr.length; i++) {
if (callback(arr, i)) {
result.push(arr[i])
}
}
return result
}
这个 filter
函数可以给任何类型的数组使用。
接口
interface Condition<T>{
(item:T[], index:number):boolean
}
类
class ArrayHelper<T> {
constructor(private arr: T[]) {
}
take(t: T[], i: number): T[] {
const result: T[] = [];
for (let j = 0; j < i; j++) {
result.push(t[j]);
}
return result
}
}
类中 T 提升到了更高的层次, 所以类中都可以使用这个 T 类型
泛型约束
泛型约束用:用于实现泛型的取值
举个栗子
假如有一个函数: printObj
只打印带有 name
属性的对象。
function printObj<T>(obj: T) {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const element = obj[key];
console.log(element)
}
}
}
我不想通过代码的方式判断参数 obj
是否是带有一个 name
属性的对象, 而是通过泛型约束实现:
interface NameHasPrototy {
name: string
}
function printObj<T extends NameHasPrototy>(obj: T) {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const element = obj[key];
console.log(element)
}
}
}
可以约束泛型 T 必须是一个至少带有 name
属性的对象(鸭子辩型法)
当调用函数时, 必须传递一个带有 name
属性的对象。
多泛型
多泛型 顾名思义
举个栗子
假如有一个 concat
函数可以合并两个数组并返回
但是函数的使用者, 可能会传递两个不同类型的数组进行合并, 所以就需要使用多泛型
contcat
函数实现
function concat<T, K>(arr1: T[], arr2: K[]): (T | K)[] {
const result: (T | K)[] = [...arr1, ...arr2]
return result
}
(T | K)[]
表示数组的每一项可能是类型 T
也可能是类型 K
总结
泛型 像是一种 函数传参的过程, 对于不确定的类型需要根据使用者传递的类型进行书写, 这是你就应该使用 泛型
转载自:https://juejin.cn/post/7085655407040921636