TypeScript真香系列-泛型

前言

TypeScript真香系列的内容将参考中文文档,但是文中的例子基本不会和文档中的例子重复,对于一些地方也会深入研究。另外,文中一些例子的结果都是在代码没有错误后编译为JavaScript得到的。如果想实际看看TypeScript编译为JavaScript的代码,可以访问TypeScript的在线编译地址,动手操作,印象更加深刻。

概念

TypeScript的泛型与其他面向对象的语言中的定义是相似的。泛型可以理解为在我们定义函数、接口或者类的时候,不预先指定其相关的类型,而是在使用的时候手动指定类型。这和TypeScript基础类型中的any是有区别的。

当我们不使用泛型的时候,可能就如下这样:

function show(name: string): string {
    return name;
}
function show(name: any): any {
    return name;
}

上面例子中用any或许没有什么问题,但是当我们想传入的参数类型和返回值类型相同时,使用any时的返回值就可能有多种情况了。还有就是我们平时也会考虑重用性,提取公共组件等。这时泛型就能派上了。下面是一个泛型函数的例子:

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

这个show函数后面的 T ,叫做类型变量,这个我们后面会介绍。这个函数我们可以用两种方法调用。
第一种为传入参数,包括类型参数:

show<string>("james"); //"james"

第二种为不包括类型参数,这种方式利用了类型推论,这个再后面的章节会讲到:

show(1); //1

泛型变量

上面所写的例子中,我们可以在“<>”中指定一个变量T,并且我们要把这个T定义在参数和返回值上。编译器在检查我们的代码的时候,我们必须在函数体中正确的使用这个通用的类型。

function show<T>(arg: T): T {
    console.log(arg.length)  //错误,类型T不存在length属性
    return arg;
}

上面的例子中,我们在打印arg的length中报错了。原因是我们在使用这个函数的时候,传入的参数可能为数字,而数字是没有length属性的。
当然,我们在调用函数的时候也要正确调用:

function show<T>(arg: T): T {
    return arg;
}
show<string>(1); //错误,参数1的类型不是string
show<>(1); //错误,参数类型不能为空

针对数组:

function show<T>(arg: T[]): T[] {
    return arg;
}
show<string>(["奥","利","给"]);  //["奥", "利", "给"]
function show<T>(arg: Array<T>): Array<T> {
    return arg;
}
show<number>([6, 6, 6]); // [6, 6, 6]

泛型接口

如下所示:

interface IMan { 
    <T>(arg: T): T
}
function man<T>(arg: T): T{
    return arg;
}
let showMan: IMan = man;
showMan("james"); //"james"

还有另外一种写法,这种方法我们可以把泛型参数当做整个接口的参数:

interface IMan<T> { 
    (arg: T): T
}
function man<T>(arg: T): T{
    return arg;
}
let showMan: IMan<string> = man;
showMan("james"); //"james"

这样的好处就是在使用时知道具体的参数类型了。

泛型类

我们可以使用(<>)和尖括号中间的变量,然后跟在类名的后面来表示泛型类。需要注意的是,泛型类指的是实例部分的类型,类的静态属性不能使用泛型类型。

class Man<T> { 
    name: T;
    constructor(arg: T) {
        this.name = arg;
    }
    play(a: T) : T {
        return a;
    }
    
}
let showMan = new Man<string>("James");
showMan.name;  //"James"
showMan.play("ball"); //"ball"

也可以稍微复杂一点:

class Man<T> { 
    name: T;
    sum: T[];
    
    constructor(arg: T) {
        this.name = arg;
        this.sum = [];
        
    }
    push(item: T): void{
        this.sum.push(item) 
    }
    output() { 
         return this.sum;
    }
}
let showMan = new Man<string>("James");
showMan.push("one for all");
showMan.output(); //["one for all"]

泛型约束

让我们回到泛型变量一节中的一个例子:

function show<T>(arg: T): T {
    console.log(arg.length)  //错误,类型T不存在length属性
    return arg;
}

如果我们想要使用length属性,那该怎么办?可以看下面的例子:

interface ILen {
    length: number;
 }
function show<T extends ILen>(arg: T): T {
    console.log(arg.length)  //没有报错了
    return arg;
}

虽然上面的例子没有再报错,但是因为这个泛型函数被定义了约束,所以我们再调用函数的时候又有了讲究,也就是我们在传值的时候需要像下面的例子一样:

interface ILen {
    length: number;
 }
function show<T extends ILen>(arg: T): T {
    console.log(arg.length)  
    return arg;
}
show(3); //错误,参数3不能分配给类型ILen 
show({ length: 2 }); // 2  {length: 2}
show({length:2, arg:1}) //2  {length: 2, arg: 1}
show({length:2, arg:"james"}) //2 {length: 2, arg: "james"}

参考

https://github.com/zhongsp/TypeScript

最后

文中有些地方可能会加入一些自己的理解,若有不准确或错误的地方,欢迎指出~

https://juejin.im/post/5e36a0846fb9a02feb5b9ae0

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论