什么是泛型

官方是这样介绍的:

软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。

在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。

从上面的信息概括为泛型是支持多种类型的变量,根据用户需求灵活的变动,达到复用的效果。

在实际开发中,函数是同样的逻辑,只是因为类型的不同,可能要再写一个函数,这样的问题很糟糕。正好泛型就可以用来解决这种问题。

举例

现在有一个处理数组的函数,这里只是简单的返回,当做进行处理

  1. function formatArr(arr:Array<string>):Array<string>{
  2. return arr.map(item=>item)
  3. }

如果这时候有其他类型的数组需要同样的操作,而 formatArr 只接受字符串数组,我们有什么方法来解决呢?

第一种:any

函数接受一个任意类型的数组,这确实能够解决问题,但是也失去了类型检查的意义

  1. function formatArr(arr:Array<any>):Array<any>{
  2. return arr.map(item=>item)
  3. }

第二种:重新定义函数

再写一个同样逻辑的函数,这看起来就不妥。如果还要接受更多的类型,而函数内部的逻辑复杂,这样重复定义多个同样逻辑的函数,会显得代码的冗余

  1. function formatArr2(arr:Array<number>):Array<number>{
  2. return arr.map(item=>item)
  3. }

正解:泛型

泛型函数的类型与非泛型函数的类型没什么不同,只是声明一个类型参数在最前面。因为类型参数相当于变量,我们不必在函数定义时就定义类型,而是执行时由使用者规定类型。

  1. function formatArr<T>(arr:Array<T>):Array<T>{
  2. return arr.map(item=>item)
  3. }
  4. formatArr<string>(['1', '2', '3'])
  5. formatArr<number>([1, 2, 3])

定义函数 formatArr 时,在函数名后定义一个类型参数 <T>,同时参数数组项也接受 T 类型的值。

在使用函数时,使用者传入的类型即为 T 的类型。

尖括号内的变量名并不是固定的,可以自定义,一般都是大写

泛型类

泛型类实例化传入的泛型类型,可以在整个作用域中使用该泛型类型,但要注意的是类的静态属性无法使用泛型类型

  1. class Handsome<T>{
  2. static myname:T = 'Jack' // 静态成员不能引用类类型参数
  3. girlfriend: Array<T>
  4. constructor(){
  5. this.girlfriend = []
  6. }
  7. addGirlfriend(name:T){
  8. this.girlfriend.push(name)
  9. }
  10. getAllGirlfriend():Array<T>{
  11. return this.girlfriend
  12. }
  13. }
  1. let handsome = new Handsome<string>()
  2. handsome.addGirlfriend('Julia')
  3. handsome.addGirlfriend('Vivian')
  4. //handsome.addGirlfriend({name:'Ellie'}) 类型“{ name: string; }”的参数不能赋给类型“string”的参数
  5. handsome.getAllGirlfriend() // ['Julia', 'Vivian']

泛型接口

使用含有泛型的接口来定义函数

  1. interface CreateArrayFunc {
  2. <T>(length: number, value: T): Array<T>;
  3. }
  4. let createArray: CreateArrayFunc;
  5. createArray = function<T>(length: number, value: T): Array<T> {
  6. let result: T[] = [];
  7. for (let i = 0; i < length; i++) {
  8. result[i] = value;
  9. }
  10. return result;
  11. }
  12. createArray(3, 'x'); // ['x', 'x', 'x']

泛型参数提前到接口名

  1. interface CreateArrayFunc<T> {
  2. (length: number, value: T): Array<T>;
  3. }
  4. let createArray: CreateArrayFunc<string>;
  5. createArray = function<T>(length: number, value: T): Array<T> {
  6. let result: T[] = [];
  7. for (let i = 0; i < length; i++) {
  8. result[i] = value;
  9. }
  10. return result;
  11. }
  12. createArray(3, 'x'); // ['x', 'x', 'x']

这种方式使用泛型接口的时候,需要定义泛型的类型

接口范围内的泛型

看到上面这两种方式定义函数,使用起来差不多,可能你会有个疑问,它们有什么区别,哪种更好?(我第一次看到的时候也会有这个疑问)

接下来,我用第二种方式再写一个例子,看看它们的区别在哪里。

  1. interface People<T>{
  2. name: T;
  3. friends: Array<T>;
  4. say: (msg:T)=>T
  5. }
  6. let student:People<string> = {
  7. name: 'Joe',
  8. friends: ['Mike','James','LuLu'],
  9. say: function(msg){
  10. return this.name+':'+msg
  11. }
  12. }

就像泛型类一样,当你需要在接口范围内多次用到泛型参数时,可以将它提前到接口名。

总的来说,以这种方式定义接口,可以统一接口内的类型,控制内部多个属性的参数类型。是不是这种就更好?不一定,如果你的需求只会用到一次泛型参数时,那就不必把泛型参数提前到接口名,因为在多人协同合作中,可能会引起其他使用者的误会。

泛型约束

泛型约束提供更智能的类型推导,为类型提供扩展。有时候我们希望泛型参数符合某些规则时,你应该想到使用泛型约束来解决问题。

基于接口约束

使用泛型约束来对 formatArr 做一些改造,改造后的函数功能为对传入的参数进行切片,返回除第一项的数据。但并不是每个类型都有 slice 方法,这时候就需要对泛型进行约束,规定只有 slice 方法的参数才可以传入。为此,定义一个含有 slice 方法的接口,使用这个接口和 extends 关键词实现约束。

  1. interface Slice{
  2. slice:Function;
  3. }
  4. function formatArr<T extends Slice>(arg:T):T{
  5. return arg.slice(1)
  6. }
  7. formatArr([1,2,3])
  8. formatArr('hello')

keyof约束

再来看一个泛型约束的例子,为函数定义两个泛型类型,T类型为对象,keyof定义U类型为T类型上的一个key值。在使用该函数,ts会进行类型推导,提示你第二个参数应该为第一个参数上的key值。

  1. function getValue<T extends object, U extends keyof T>(obj:T, key:U){
  2. return obj[key]
  3. }
  4. let obj = {
  5. color: 'white',
  6. size: 'big'
  7. }
  8. getValue(obj, "color") //ok
  9. getValue(obj, "name") //error 类型“"name"”的参数不能赋给类型“"color" | "size"”的参数

上面的例子可能你会想到接口继承,因为接口继承也使用了 extends 关键词,要注意在泛型约束里extends并不是表示继承关系。

泛型参数的默认类型

在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。就像 ES6 中的函数默认参数一样,为代码增加健壮性。

  1. function createArray<T = string>(length: number, value: T): Array<T> {
  2. let result: T[] = [];
  3. for (let i = 0; i < length; i++) {
  4. result[i] = value;
  5. }
  6. return result;
  7. }

写在最后

通过以上使用泛型的几个例子,不难发现泛型的强大,可变的类型变量和泛型约束为 TypeScript 的类型推导都提供了很大的贡献。开发者根据类型提示能轻松知道怎么调用其他开发者封装的方法,像是基于文档编程的感觉,这也是为什么我们说在多人开发中,TypeScript 可以提高开发效率。类型是 TypeScript 的核心,也是它的魅力所在。理解并应用泛型,可以使我们的 TypeScript 水平更上一层楼。

TypeScript - 泛型的更多相关文章

  1. TypeScript 泛型及应用

    TypeScript 泛型及应用 一.泛型是什么 二.泛型接口 三.泛型类 四.泛型约束 4.1 确保属性存在 4.2 检查对象上的键是否存在 五.泛型参数默认类型 六.泛型条件类型 七.泛型工具类型 ...

  2. TypeScript 泛型(generic) 入门介绍

    TypeScript 泛型函数 下面来创建第一个使用泛型的例子:identity函数.这个函数会返回任何传入它的值.你可以把这个函数当成是echo命令.不用泛型的话,这个函数可能是下面这样: func ...

  3. 【第7篇】TypeScript泛型的案例代码具体解释

    8.1最简单泛型样例 Ts代码 /** * 没有泛型,我们要么必须给身份功能的特定类型 */ function identity1(arg: number): number { return arg; ...

  4. typescript泛型(学习笔记非干货)

    软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性. 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型, 这在创建大型系统时为你提供了十分灵活的功能. In softwa ...

  5. TypeScript泛型

    泛型的概念 指不预先确定的数据类型,具体的类型要在使用的时候才能确定.咋一听,是不是觉得JavaScript本身就是这样?这是由于理解有误.前面说“在使用的时候确定”,而非在程序执行的时候确定. 泛型 ...

  6. 十分钟教你理解TypeScript中的泛型

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者.原文出处:https://blog.bitsrc.io/understanding-generics-in-t ...

  7. 《三》大话 Typescript 接口

    > 前言: 本文章为 TypeScript 系列文章. 旨在利用碎片时间快速入门 Typescript. 或重新温故 Typescript 查漏补缺.在官方 api 的基础上, 加上一些日常使用 ...

  8. Typescript 最佳实践

    文章列表: <一>大话 TypeScript 基本类型 <二>大话 Typescript 枚举 <三>大话 Typescript 接口 <四>大话 Ty ...

  9. 深入浅出 Typescript 学习笔记

    TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准. TypeScript 由微软开发的自由和开源的编程语言. TypeScript 设计目标是开发大型应 ...

随机推荐

  1. 某些机root也不能访问dma-buf

    从4.3后,回顾<从surfaceflinger历史变更谈截屏>,只能通过生产消费者队列向surfaceflinger服务申请显示缓冲,这个缓冲就是dma-buf映射的共享内存. bind ...

  2. 构思一个在windows下仿objc基于动画层ui编程的ui引擎

    用c/c++编程有些年了,十个指头可以数齐,在涉入iOS objc开发后,有种无比舒服的感觉,尤其在UI开发上. 在QuartzCore.framework下动画和透明窗口等许多效果的事都变得那么方便 ...

  3. href=”javascript:void(0);

    href=”javascript:void(0);”这个的含义是,让超链接去执行一个js函数,而不是去跳转到一个地址,而void(0)表示一个空的方法,也就是不执行js函数. 为什么要使用href=” ...

  4. 部署helm服务

    helm在ocp中相当于catalog中的template k8s中使用helm之前遇到的问题 .很难管理.编辑和维护如此多的服务.每个服务都有若干配置,缺乏一个更高层次的工具将这些配置组织起来. . ...

  5. AE ArcEngine10.4+vs2012安装配置

    准备内容 安装环境:win10*64位专业版,ArcGIS_Desktop_1041_151727,C#语言环境,visual studio2012 安装文件:ArcGIS_Engine_1041_1 ...

  6. 预训练语言模型整理(ELMo/GPT/BERT...)

    目录 简介 预训练任务简介 自回归语言模型 自编码语言模型 预训练模型的简介与对比 ELMo 细节 ELMo的下游使用 GPT/GPT2 GPT 细节 微调 GPT2 优缺点 BERT BERT的预训 ...

  7. IDEA用Maven连接MySQL的jdbc驱动,并操作数据库

    1.在IDEA里创建Maven项目 1.1.点击Create New Project   1.2.选择Maven,JDK这里用的是1.8,点击Next  1.3.填入“组织名”.“项目名”,版本是默认 ...

  8. Linux的curl和wget

    wget wget命令用来从指定的URL下载文件.wget非常稳定,它在带宽很窄的情况下和不稳定网络中有很强的适应性,如果是由于网络的原因下载失败,wget会不断的尝试,直到整个文件下载完毕.如果是服 ...

  9. ctf比赛linux文件监控和恢复shell

    之前参加ctf比赛时候临时写的,有很多不足,不过可以用,就贴出来分享给大家,希望对大家有帮助. 脚本一:记录当前目录情况 #!/bin/bashfunction getdir(){    for el ...

  10. MySQL 高可用架构 之 MHA (Centos 7.5 MySQL 5.7.18 MHA 0.58)

    目录 简介 环境准备 秘钥互信 安装基础依赖包 安装MHA组件 安装 MHA Node组件 安装 MHA Manager 组件 建立 MySQL 一主三从 初始化 MySQL 启动MySQL 并简单配 ...