TypeScript教程(五)– 泛型

​本文介绍通用及可重用的类型,包括泛型类,泛型函数,泛型接口,泛型约束,以及类型映射。


问题是什么

首先我们要理解,泛型要解决什么问题。

假设我们构建一个表示键值对的类KeyValuePair。键的类型是 number, 值 的类型是 string。

如果键的类型变成 string,比如我们想定义:

let keyValuePair = new KeyValuePair(“one”,”TypeScript”);

编译器提示类型不对,我们有二个选择。

第一个,我们可以将key的类型改成 any, 之前我们说过,要避免使用 any 类型,显然这个选择不太好。

第二个,我们再创建一个StringKeyValuePair,定义 key 的类型为 string。但当类的键类型或值类型变化时,我们又要创建新类,没有尽头。

所以我们需要一个通用的类型。


泛型类

我们修改前面的KeyValuePair类。

用尖括号包含T <T>代表传递的类型,不一定是T,可以是任何字母,T 只是 Template的缩写而已。我们使用时传递 <string>给<T>,就可以使用类型为string的Key了,当然可以传递任何类型。

再进一步,我们将Key 和 Value都改成泛型。

使用泛型类,我们不必重复写代码。

使用泛型类时,可以指定类型,也可以不必指定,编译器自动感知。绝大多数情况下都是无需指定类型的,由编译器自动识别就好。


泛型函数

泛型函数很类似,看个例子

wrapInArray是个泛型函数,我们也用字母T表示泛类型。调用该函数也不必传递具体类型,编译器自动感知。

可以将泛型函数放在类 class 里,如下。


泛型接口

假设有两个API端点:

http://mywebsite.com/users

http://mywebsite.com/products

访问获取的数据分别是user列表和product列表。可以创建一个接口interface Result来表示获取的数据结果,但我们不希望指定Product或User类型,因为失去了重用性。

我们使用泛型来解决。

fetch函数返回的接口Result使用了泛型,调用fetch函数时,传递User类型,编译器自动返回User列表;传递Product类型,则自动返回Product列表,非常灵活。


泛型约束

看下面这个泛型函数echo,我们可以传任何类型的值给它都可以。

function echo<T>(value: T) {

    return value;

}

而有些时候是需要约束泛型的。

比如需要约束只能是number 或 string类型,可以使用

T extends number | string

也可以约束为某对象类型。

T extends {name: string}

当然也可以约束为某个类。


泛型类的扩展

创建一个电子商务应用,由用户User,Product产品,ShoppingCart购物车等对象组成。

再创建一个Store类,用于保存Product等这些对象。

现在我们有了基本的泛型类Store,如何扩展?

一般有三种情况。

第一种,无限制传递泛型参数。比如我们要扩展一个能压缩类CompressibleStore,要扩展类CompressibleStore的泛型参数传递到Store类。

class CompressibleStore<T> extends Store<T>

第二种,限制泛型结构传递参数。比如我们要扩展一个搜索类SearchableStore,由于要搜索属性name,所以就要限制泛型T必须有属性name,

<T extends { name: string }>

这样只有符合该规范的泛型都可以传递过来,比如User,ShoppingCart,只要有name属性都是允许的。

第三种,固定的泛型参数。比如我们要扩展一个ProductStore类,那就只允许泛型传递Product接口或其扩展子接口和实现类。

<T extends Product>


操作符 keyof

接着上节的例子,我们给Store类添加find方法。

find方法接收 property 和 value参数,根据这两个参数来查找存储的对象。这里有个问题,如果接受的property参数不是泛型类的属性,程序就会崩溃。

使用keyof确保property参数是泛型类的属性。


类型映射

有时我们需要基于某个类型构建另一个类型。看一个例子。

Product接口有name,price两个属性。现在要创建另一个接口ReadOnlyProduct,需要将name和price属性都改成只读。

如果手动再创建ReadOnlyProduct,明显代码是有重复的。

可以使用类型映射来解决。

类型映射结合了索引签名和keyof来实现。

我们显然不满足仅将Product改成ReadOnlyProduct,我们希望是通用的,可以将任何其他接口的属性改成只读的。

我们将Product改成泛型<T>,同时为了简洁,将Property改成字母K。

同样道理,我们也可以映射成Optional,  Nullable,可能性是无限的。

正因为类型映射好处太多了,TypeScript已内置了一些类型。 

谷歌搜索”typescript utility types”, 查看网页。

https://www.typescriptlang.org/docs/handbook/utility-types.html

我们看到,里面的 Partial<Type>就是我们的Optional<T>,Readonly<Type>就是我们建的ReadOnly<T>,还有很多其他有用的类型映射比如Record<Keys, Type>等,这些在TypeScript中都是内置的,直接可以使用。


小结

本文我们介绍了泛型类,泛型函数,泛型接口,泛型约束,以及类型映射等知识。

下一篇介绍装饰器。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注