原来TS的泛型如此好用😁😌

2,014 阅读3分钟

在前端开发中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。这时候ts的泛型就起到了关键的作用。

一:泛型: 基本用法

(1): 常规使用

现在有个需求:封装一个方法,接收一个参数(number类型),return一个相同类型的result。

   const indentify1 = (args: number): number => {
       return args;
   }

如果接收的类型是number或者string呢?

   const indentify2 = (args: number | string): number | string => {
       return args;
   }

如果参数类型越来越多怎么处理?泛型可以解决。

    const indentify3 = <T>(args: T): T => {
        return args;   // T类似一个变量保证入参和出参类型一致。
    }

泛型起到约束作用。泛型不只可以提供一个。

    const indentify4 = <T, U>(args: T, mes: U): T => {
        return args;  // 提供一个T和U泛型,声明入参类型,指定返回结果类型。
    }

(2): 泛型接口

如上indentify4函数,若是函数返回的是多类型集合,泛型如何处理?

方法一:返回类型集合

    const indentify5 = <T, U>(args: T, mes: U): [T,U] => {
        return [args, mes];   // 约束太多,外层对泛型的约束
    }

方法二: 泛型接口

可以提供一个接口,把接口内的参数变为泛型。举个🌰: 定义一个person函数,接收姓名,年龄,性别,是否结婚。最后return。

    interface IPersonView<T, U, V> {
        name: T,
        age: U,
        sex: T,
        isMarray: V
    }
    
    const per = <T, U, V>(name: T, age: U, sex: T, isMarray: V):IPersonView<T, U, V> => {
         let result: IPersonView<T, U, V> = {
             name, age, sex, isMarray
         }
         return result;
    }

可以看出使用泛型接口可以很友好的解决这些问题。便于维护和统一。

二:常用的操作符

(1): extends

很简单就是继承的意思,让一个类型变量继承我们定义好的类型。在泛型约束中起到了很大的作用。(之后会说到泛型约束)

(2): keyof

该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。举个🌰:

    interface Person {
        name: string;
        age: number;
        isMarray: boolean;
    }
    type per1 = keyof Person   // string | number | boolean

三:泛型约束

有时我们可能希望限制每个类型变量接受的类型数量,这就是泛型约束的作用。起到对类型变量限制作用。

举个🌰:处理字符串或数组时,我们会假设 length 属性是可用的。当我们使用函数并尝试输出参数的长度,会出现一些问题。

    const indentify5 = <T>(args: T):T => {
        console.log(args.length);   // // 类型“T”上不存在属性“length"
        return args;
    }

出现这个问题的原因是,因为ts编译器不知道,不能识别T上是否含有某个属性。我们可以定义一个类型,让类型变量extends。

    interface ILength {
        length: number   // 定义一个接口包含length
    }
    
    const indentify6 = <T extends ILength>(args: T):T => {
        console.log(args.length);  // number
        return args;
    }

当然了:对上面这种情况我们也可以定义数组类型来解决。

    const indentify7 = <T>(args: T[]): T[] => {
        console.log(args.length);
        return args;
    }
    
    const indentify8 = <T>(args: Array<T>): Array<T> => {
        console.log(args.length);
        return args;
    }

我们也可以通过keyof来确实对象上的键是否存在。举个🌰:

    声明一个类型接口。
    interface Ip {
        name: string,
        sex: string,
        age: number
    }
    // 通过 keyof 操作符,我们就可以获取指定类型的所有键,之后我们就可以结合extends约束
    //即限制输入的属性名包含在 keyof 返回的联合类型中
    
    const indentify9 = <IPerson, K extends keyof Ip>(obj: Ip , key: K): Ip[k] {
        return obj[key];
    }

泛型工具

(1): Partial

将某个类型里的所有属性变成非必需属性。

    type Partial<T> = {
        [P in keyof T]?: T[P];   // 把!变成?
    };

举个🌰:

    interface IPerson {
        name: string,
        age: number
    }
    Partial<IPerson> ===  interface IPerson {
                              name?: string | undefined,
                              age: number | undefined
                          }

(2): Exclude

将T中某些属于U的类型移除掉

     type Exclude<T, U> = T extends U ? never : T; 
    type T = Exclude<"a" | "b" | "c", "a">   // "b" | "c"

(3): ReturnType

ReturnType 的作用是用于获取函数 T 的返回类型。

type T1 = ReturnType<() => String>  // string
type T2 = ReturnType<(args: String) => void>  // void
type T3 = ReturnType<<T>() => T>; // {}

以上就是一些简单的使用,有不足欢迎大家指出。