泛型

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

泛型比较广泛的说法是“参数化类型”。就是将类型作为参数传入到方法。泛型通常以<>来包裹。

泛型函数

比如,我有个函数 接受一个任意参数,并将其直接返回出去

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

const res = test(123)
console.log(res);

在这个例子中,参数arg是any类型 参数类型信息将丢失。而返回类型也是any 返回值类型亦丢失。

接下来,我们开始引入一个叫做泛型的东西,我们给test添加了类型的变量T用<T>表示
T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。
之后我们再次使用了 T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。
这允许我们跟踪函数里使用的类型的信息。

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

const res = test<number>(123)
console.log(res);

实际上我们也可以利用ts本身的类型推断 不用传入所有参数使用

// 编译器会根据传入的参数自动地帮助我们确定T的类型
const res = test(123)
console.log(res);

泛型类

类也可以使用泛型,泛型类 也是使用<>括起泛型类型,跟在类名后面。

// 创建一个人
class People {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}
const people = new People('小红', 18);

// 测试
class JsonResult<T> {
    code: number;
    msg: string;
    data: T;
    constructor(code: number, msg: string, data: T){
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
}

const res = new JsonResult<People>(0, '成功', people);

除了上边,根据业务需求封装的基于泛型可复用的组件外,泛型还可以起到约束,再来单独简单的demo

// 测试
class Test<T> {
    data: T;
}

const test = new Test<string>();
test.data = 123; // 错误:不能将类型“number”分配给类型“string”

有一点需要注意:类的静态成员不能使用这个类上的泛型类型。

其他

泛型类型(泛型接口)

在学习泛型函数的时候,我们知道,如下方式即可定一个

function fn<T>(arg: T): T {
    return arg;
}
fn<string>('你好');

如上,我们的函数需要的类型(如参数类型、返回值类型)都是直接写在上边的。
我们怎么提取出来呢,即如何定义函数本身的类型呢?
其实它(带泛型的函数)与函普通函数的类型定义也没什么不同,只是有一个类型参数在最前面

let myFn: <T>(arg: T) => T =  (arg)=> {
    return arg;
};

我们还可以使用带有调用签名的对象字面量来定义泛型函数:

let myFn: {<T>(arg: T) => T} = (arg)=> {
    return arg;
};

这引导我们去写第一个泛型接口了。 我们把上面例子里的对象字面量拿出来做为一个接口:

interface FnType {
    <T>(arg: T): T;
}
let myFn: FnType = (arg)=> {
    return arg;
};

我们可能想把泛型参数当作整个接口的一个参数,
这样我们就能清楚的知道使用的具体是哪个泛型类型。
这样接口里的其它成员也能知道这个参数的类型了。

interface FnType<T> {
    (arg: T): T;
}


let myFn: FnType<number> = (arg)=> {
    return arg;
};
myFn('abc'); // 错误:类型“string”的参数不能赋给类型“number”的参数

如上。 不再描述泛型函数,而是把非泛型函数签名作为泛型类型一部分。
当我们使用 myFn的时候,还得传入一个类型参数来指定泛型类型(这里是:number),锁定了之后代码里使用的类型。
对于描述哪部分类型属于泛型部分来说,理解何时把参数放在调用签名里和何时放在接口上是很有帮助的。

泛型约束

有时候想操作某类型的一组值,并且我们知道这组值具有什么样的属性。 如下 想访问arg的length属性,但是编译器并不能证明每种类型都有length属性,所以就报错了。

function test<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}

为此,我们定义一个接口来描述约束条件。 创建一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束

interface Lengthwise {
    length: number;
}

function test<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // 现在我们知道它有 一个 .length 的属性, 所以不会报错
    return arg;
}

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

test(3);  // 错误:类型“number”的参数不能赋给类型“Lengthwise”的参数
test({length: 10, value: 3});