Typescript入门手册之泛型

Posted 余光、

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Typescript入门手册之泛型相关的知识,希望对你有一定的参考价值。

【Typescript入门手册】之泛型

🚀【TypeScript入门手册】记录了出场率较高的Ts概念,旨在帮助大家了解并熟悉Ts
🎉 本系列会持续更新并更正,重点关照大家感兴趣的点,欢迎同学留言交流,在进阶之路上,共勉!
👍 star本项目给作者一点鼓励吧
📚 系列文章,收藏 不走丢哦

一、泛型(Generics)

软件工程的一个重要部分就是构建组件,组件不仅需要有定义良好和一致的API,也需要是可复用的(reusable)。好的组件不仅能够兼容今天的数据类型,也能适用于未来可能出现的数据类型,我们可以创建一个支持众多类型的组件,这让用户可以使用自己的类型消费(consume)这些组件。

让我们开始写第一个泛型,一个恒等函数(identity function)。所谓恒等函数,就是一个返回任何传进内容的函数。你也可以把它理解为类似于 echo 命令。

function identity(arg: number): number 
    sreturn arg;

我们需要一种可以捕获参数类型的方式,然后再用它表示返回值的类型。这里我们用了一个类型变量(type variable),一种用在类型而非值上的特殊的变量。

function identity<Type>(arg: Type): Type 
    return arg;

现在我们已经给恒等函数加上了一个类型变量Type,这个Type允许我们捕获用户提供的类型,使得我们在接下来可以使用这个类型。这里,我们再次用Type作为返回的值的类型。在现在的写法里,我们可以清楚的知道参数和返回值的类型是同一个

在我们写了一个泛型恒等函数后,我们有两种方式可以调用它。第一种方式是传入所有的参数,包括类型参数:

let output = identity<string>("myString");

第二种方式可能更常见一些,这里我们使用了类型参数推断(type argument inference)我们希望编译器能基于我们传入的参数自动推断和设置Type的值。

let strA = identity('isString'); // let strA: string

注意这次我们并没有用<Type>明确的传入类型,当编译器看到isString这个值,就会自动设置Type为它的类型(即 string)。

类型参数推断是一个很有用的工具,它可以让我们的代码更短更易阅读。而在一些更加复杂的例子中,当编译器推断类型失败,你才需要像上一个例子中那样,明确的传入参数。

二、使用泛型类型变量(Working with Generic Type Variables)

我们在上一节了解到的例子中,Type是直接作为类型进行传递的,而在某些时候Type也可以是类型的某一个参数,例如:

function loggingIdentity<Type>(arg: Type[]): Type[] 
    console.log(arg.length);
    console.log(arg[0]);
    return arg;

你可以这样理解loggingIdentity的类型:泛型函数loggingIdentity接受一个 Type 类型参数和一个实参arg,实参arg 是一个Type类型的数组。而该函数返回一个Type类型的数组。

现在我们使用类型变量 Type,是作为我们使用的类型的一部分,而不是之前的一整个类型,这会给我们更大的自由度。

三、泛型类型 (Generic Types)

泛型函数的形式就跟其他非泛型函数的一样,都需要先列一个类型参数列表,这有点像函数声明:

function identity<Type>(arg: Type): Type 
    console.log('>>:', arg);
    return arg;

let myIdentity: <T>(arg: T) => T = identity;

注意:不要对<T>感到疑惑,泛型的类型参数(Type || T || XX)可以使用不同的名字,只要数量和使用方式上一致即可。

我们也可以以对象类型的调用签名的形式,书写这个泛型类型:

function identity<Type>(arg: Type): Type 
    return arg;


let myIdentity:  <Type>(arg: Type): Type  = identity;

这可以引导我们写出第一个泛型接口,让我们使用上个例子中的对象字面量,然后把它的代码移动到接口里:

interface GenericIdentityFn 
    <Type>(arg: Type): Type;

 
function identity<Type>(arg: Type): Type 
    return arg;

 
let myIdentity: GenericIdentityFn = identity;

四、泛型类(Generic Classes)

泛型类写法上类似于泛型接口。在类名后面,使用尖括号中 <> 包裹住类型参数列表:

class GenericNumber<NumType> 
    zeroValue: NumType;
    add: (x: NumType, y: NumType) => NumType;


let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) 
    return x + y;
;

在这个例子中,并没有限制你只能使用number类型。我们也可以使用其他类型:

type strOrNum = string | number;

class GenericNumber<NumType> 
    zeroValue: NumType;
    log: (x: NumType, y: NumType) => NumType;


let myGenericNumber = new GenericNumber<strOrNum>();
myGenericNumber.zeroValue = 0;
myGenericNumber.log = function(x, y) 
    console.log(x, y);
    return `$x$y`;
    // (parameter) x: strOrNum
    // (parameter) y: strOrNum
;

就像接口一样,把类型参数放在类上,可以确保类中的所有属性都使用了相同的类型。

五、泛型约束(Generic Constraints)

在早一点的 第三章函数中 我们想要获取参数obj.length 属性,但是编译器并不能证明每种类型都有 .length 属性,相比于能兼容任何类型,我们更愿意约束这个函数,让它能使用带有.length 属性的类型。只要类型有这个成员,我们就允许使用它,但必须至少要有这个成员。

function longest<Type extends  length: number >(a: Type, b: Type) 
    if (a.length >= b.length) 
        return a;
     else 
        return b;
    

我们也可以列出对 Type 约束中的必要条件,为此,我们需要创建一个接口,用来描述约束。这里,我们创建了一个只有 .length 属性的接口,然后我们使用这个接口和 extend关键词实现了约束:

interface Lengthwise 
  length: number;

function longest<Type extends Lengthwise>(a: Type, b: Type) 
    if (a.length >= b.length) 
        return a;
     else 
        return b;
    

5.1 在泛型约束中使用类型参数(Using Type Parameters in Generic Constraints)

你可以声明一个类型参数,这个类型参数被其他类型参数约束。

举个例子,我们希望获取名的值,一个对象给定属性为此,我们需要确保我们不会获取obj上不存在的属性。所以我们在两个类型之间建立一个约束:

function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) 
    return obj[key];


let x =  a: 1, b: 2, c: 3, d: 4 ;

getProperty(x, "a");
getProperty(x, "m");
// 类型“"m"”的参数不能赋给类型“"a" | "b" | "c" | "d"”的参数。

5.2 在泛型中使用类类型(Using Class Types in Generics)

在 TypeScript 中,当使用工厂模式创建实例的时候,有必要通过他们的构造函数推断出类的类型,举个例子:

function create<Type>(c:  new (): Type ): Type 
    return new c();


class BeeKeeper 
    hasMask: boolean = true;


class ZooKeeper 
    nametag: string = "Mikle";


class Animal 
    numLegs: number = 4;


class Bee extends Animal 
    keeper: BeeKeeper = new BeeKeeper();


class Lion extends Animal 
    keeper: ZooKeeper = new ZooKeeper();


function createInstance<A extends Animal>(c: new () => A): A 
    return new c();


createInstance(Lion).keeper.nametag;
createInstance(Bee).keeper.hasMask;

写在最后

本篇文章是《Typescript基础入门》第五篇,其实这篇文章已经需要花费更多的精力才能“了解”了,不妨多读几遍,记得star哦~

参考:

关于我:

  • 花名:余光
  • 邮箱:webbj97@163.com
  • csdn:传送门

其他沉淀:

以上是关于Typescript入门手册之泛型的主要内容,如果未能解决你的问题,请参考以下文章

Typescript入门手册之泛型

java遗珠之泛型类型推断

java遗珠之泛型类型推断

TypeScript专题之泛型

转职成为TypeScript程序员的参考手册

泛型之泛型方法