本文介绍通用及可重用的类型,包括泛型类,泛型函数,泛型接口,泛型约束,以及类型映射。
问题是什么
首先我们要理解,泛型要解决什么问题。
假设我们构建一个表示键值对的类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中都是内置的,直接可以使用。
小结
本文我们介绍了泛型类,泛型函数,泛型接口,泛型约束,以及类型映射等知识。
下一篇介绍装饰器。