likes
comments
collection
share

8、TS中的泛型

作者站长头像
站长
· 阅读数 25

多个位置的类型信息应该保持一直或有关联的信息。

泛型

泛型: 是指附属于函数、类、接口、类型别名之上的类型。

在函数中使用泛型

假如有这样一种场景:

有一个函数 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
评论
请登录