TS的泛型

TypeScript


泛型

使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据

泛型就是解决类 接口 方法的复用性,以及对不特定数据类型的支持

泛型可以解决any类型放弃类型检查的问题 使传入的参数类型和返回的参数类型一致


举个例子,比如我们现在有个这样的需求,我们要实现一个这样的函数,函数的参数可以是任何值,返回值就是将参数原样返回,并且参数的类型是 string,函数返回类型就为 string?

很容易写下:

    function getValue(arg:string):string  {
        return arg;
    }

现在需求有变,需要返回一个 number 类型的值,你会说,联合类型就完事了:

    function getValue(arg:string | number):string | number  {
        return arg;
    }

但是这样又有一个问题,就是如果我们需要返回一个 boolean 类型,string 数组甚至任意类型呢,难道有多少个就写多少个联合类型?

是的,我们直接用 any 就行了!

    function getValue(arg:any):any  {
        return arg;
    }

尽管 any 大法好,很多时候 any 也确实能够解决不少问题

但是这样也不符合我们的需求了,传入和返回都是 any 类型,传入和返回并没有统一,我们还能不能有其他解决办法呢?

这个时候就要祭出我们的泛型了



基本使用

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

上面的需求,我们如果用泛型来解决的话:

    function getValue<T>(arg:T):T  {
        return arg;
    }

泛型的语法是尖括号<> 里面写类型参数,一般用 T 来表示第一个类型变量名称,其实它可以用任何有效名称来代替

泛型就像一个占位符一个变量,在使用的时候我们可以将定义好的类型像参数一样传入,原封不动的输出

使用:

我们有两种方式来使用:

定义要使用的类型,比如:

    getValue<string>('树哥'); // 定义 T 为 string 类型

利用 typescript 的类型推断,比如:

    getValue('树哥') // 自动推导类型为 string



多个参数

其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U

    function getValue<T, U>(arg:[T,U]):[T,U] {
        return arg;
    }

使用:

    const str = getValue(['树哥', 18]);

typescript 给我们自动推断出输入、返回的类型



箭头函数
const foo = <T,>(x: T): T => x;

const foo = <T extends {}>(x: T): T => x;

const foo = <T extends Record<string, unknown>>(x: T): T => x;

const foo: <T>(x: T) => T = x => x;

const identity = <T,>(arg: T): T => {
    console.log(arg);
    return arg;
};

const renderAuthorize = <T>(Authorized: T): ((currentAuthority: CurrentAuthorityType) => T) => (
    currentAuthority: CurrentAuthorityType,
  ): T => {
     return
 };



泛型约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:

    function getLength<T>(arg:T):T  {
        console.log(arg.length); // 报错,不能调用 length 属性
    }

因为泛型 T 不一定包含属性 length,那么我想 getLength 这个函数只允许传入包含 length 属性的变量,该怎么做呢

这时,我们可以使用extends关键字来对泛型进行约束

    interface Lengthwise {
        length: number;
    }

    function getLength<T extends Lengthwise>(arg:T):T  {
        console.log(arg.length); 
        return arg;
    }

使用

    const str = getLength('树哥')
    const arr = getLength([1,2,3])
    const obj = getLength({ length: 5 })

这里可以看出,不管你是 strarr 还是obj,只要具有 length 属性,都可以



泛型接口

在定义接口的时候指定泛型

    interface KeyValue<T,U> {
        key: T;
        value: U;
    }

    const person1:KeyValue<string,number> = {
        key: '树哥',
        value: 18
    }
    const person2:KeyValue<number,string> = {
        key: 20,
        value: '张麻子'
    }



泛型类
    class Test<T> {
        value: T;
        add: (x: T, y: T) => T;
    }

    let myTest = new Test<number>();
        myTest.value = 0;
        myTest.add = function (x, y) {
        return x + y;
    };



泛型类型别名
    type Cart<T> = { list: T[] } | T[];
    let c1: Cart<string> = { list: ["1"] };
    let c2: Cart<number> = [1];



泛型参数的默认类型

我们可以为泛型中的类型参数指定默认类型。

当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。有点 js 里函数默认参数的意思。

    function createArray<T = string>(length: number, value: T): Array<T> {
        let result: T[] = [];
        for (let i = 0; i < length; i++) {
            result[i] = value;
        }
        return result;
    }




泛型工具类型
typeof

关键词除了做类型保护,还可以从实现推出类型,

    //先定义变量,再定义类型
    let p1 = {
        name: "树哥",
        age: 18,
        gender: "male",
    };
    type People = typeof p1;
    function getName(p: People): string {
        return p.name;
    }
    getName(p1);

keyof

可以用来获取一个对象接口中的所有 key 值

    interface Person {
        name: string;
        age: number;
        gender: "male" | "female";
    }

    type PersonKey = keyof Person; //type PersonKey = 'name'|'age'|'gender';

    function getValueByKey(p: Person, key: PersonKey) {
        return p[key];
    }
    let val = getValueByKey({ name: "树哥", age: 18, gender: "male" }, "name");
    console.log(val); // 树哥

in

用来遍历枚举类型:

    type Keys = "a" | "b" | "c"

    type Obj =  {
        [p in Keys]: any
    } // -> { a: any, b: any, c: any }

infer

在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。

    type ReturnType<T> = T extends (
    ...args: any[]
    ) => infer R ? R : any;

infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。


extends

有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。

    interface Lengthwise {
        length: number;
    }

    function loggingIdentity<T extends Lengthwise>(arg: T): T {
        console.log(arg.length);
        return arg;
    }

现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:

    loggingIdentity(3);  // Error, number doesn't have a .length property

当我们传入合法的类型的值,即包含 length 属性的值时:

    loggingIdentity({length: 10, name: '张麻子'}); // 编译正确

索引访问操作符

使用 [] 操作符可以进行索引访问:

    interface Person {
        name: string;
        age: number;
    }

    type x = Person["name"]; // x is string




内置工具类型
Required

将类型的属性变成必选

    interface Person {
        name?: string,
        age?: number,
        hobby?: string[]
    }

    const user: Required<Person> = {
        name: "树哥",
        age: 18,
        hobby: ["code"]
    }

Partial

与 Required 相反,将所有属性转换为可选属性

    interface Person {
        name: string,
        age: number,
    }

    const shuge:Person = {
        name:'树哥'
    } // error  Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.

从上面知道,如果必传而我们少穿传了的话,就会报错

我们使用 Partial 将其变为可选

    type User = Partial<Person>

    const shuge: User={
        name:'树哥'
    } // 编译正确

Exclude

Exclude<T, U> 的作用是将某个类型中属于另一个的类型移除掉,剩余的属性构成新的类型

    type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
    type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
    type T2 = Exclude<string | number | (() => void), Function>; // string | number

Extract

Exclude 相反,Extract<T,U>T 中提取出 U

    type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
    type T1 = Extract<string | number | (() => void), Function>; // () =>void

适用于:并集类型


Readonly

把数组或对象的所有属性值转换为只读的,这就意味着这些属性不能被重新赋值。

    interface Person {
        name: string;
        age: number;
        gender?: "male" | "female";
    }

    let p: Readonly<Person> = {
        name: "hello",
        age: 10,
        gender: "male",
    };
    p.age = 11; // error  Cannot assign to 'age' because it is a read-only property.


Record

Record<K extends keyof any, T> 的作用是将 K 中所有的属性的值转化为 T 类型。

    type Property = 'key1'|'key2'
    type Person = Record<Property, string>;

    const p: Person = {
        key1: "hello 啊",
        key2: "树哥",
    };

Pick

从某个类型中挑出一些属性出来

    type Person = {
        name: string;
        age:number;
        gender:string
    }

    type P1 = Pick<Person, "name" | "age">; // { name: string; age: number; }

    const user:P1={
        name:'树哥',
        age:18
    }

Omit

Pick相反,Omit<T,K> 从T中取出除去K的其他所有属性。

    interface Person {
        name: string,
        age: number,
        gender: string
    }
    type P1 = Omit<Person, "age" | "gender">
    const user:P1  = {
        name: '树哥'
    }

NonNullable

去除类型中的 nullundefined

    type P1 = NonNullable<string | number | undefined>; // string | number
    type P2 = NonNullable<string[] | null | undefined>; // string[]

ReturnType

用来得到一个函数的返回值类型

    type Func = (value: string) => string;
    const test: ReturnType<Func> = "1";

Parameters

用于获得函数的参数类型所组成的元组类型。

    type P1 = Parameters<(a: number, b: string) => void>; // [number, string]

InstanceType

返回构造函数类型T的实例类型

    class C {
        x = 0;
        y = 0;
    }

    type D = InstanceType<typeof C>;  // C
    
  	type T1 = InstanceType<new (s?: string) => object>; // object

ThisType

声明this对象类型

// 没有ThisType情况下
const foo = {
    bar() {
         console.log(this.a); // error,在foo中只有bar一个函数,不存在a
    }
}

// 使用ThisType
const foo: { bar: any } & ThisType<{ a: number }> = {
    bar() {
         console.log(this.bar) // error,因为没有在ThisType中定义
         console.log(this.a); // ok
    }
}

foo.bar // ok
foo.a // error,在外面的话,就跟ThisType没有关系了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Raccom

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值