TypeScript进阶知识之泛型(泛型的定义、为什么要使用泛型、泛型的使用、泛型变量、多个类型参数、泛型类、泛型接口、泛型参数默认类型、泛型约束)

本文主要是介绍TypeScript进阶知识之泛型(泛型的定义、为什么要使用泛型、泛型的使用、泛型变量、多个类型参数、泛型类、泛型接口、泛型参数默认类型、泛型约束),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

系列文章目录

引入一:Typescript基础引入(基础类型、元组、枚举)
引入二:Typescript面向对象引入(接口、类、多态、重写、抽象类、访问修饰符)
第一章:Typescript基础知识(Typescript介绍、搭建TypeScript环境、基本数据类型)
第二章:Typescript常用类型(任意值any、数组Array、函数Function、元组Tuple、类型推论、联合类型)
第三章:Typescript基础知识(类型断言、类型别名、字符串字面量类型、枚举、交叉类型)
第四章:Typescript基础知识(类型拓宽、类型缩小)
第五章:TypeScript进阶知识之类(类的定义、类的基本使用、类的构造函数、类的属性和方法、访问修饰符、类的继承、抽象类)
第六章:TypeScript进阶知识之接口(接口定义、接口属性、可索引类型、接口表示函数类型、额外的属性检查、接口继承、接口与类型别名的区别)
第七章:TypeScript进阶知识之泛型(泛型的定义、为什么要使用泛型、泛型的使用、泛型变量、多个类型参数、泛型类、泛型接口、泛型参数默认类型、泛型约束)


文章目录

  • 系列文章目录
  • 一、泛型的定义
  • 二、为什么要使用泛型
  • 三、泛型的使用
  • 四、泛型变量
  • 五、多个类型参数
  • 六、泛型类
  • 七、泛型接口
  • 八、泛型参数默认类型
  • 九、泛型约束

一、泛型的定义

泛型(Generics) 是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

二、为什么要使用泛型

需求:实现一个函数,传入的函数参数是什么类型的,返回值的类型也要跟函数参数的类型相同,并且函数只能接收一个参数,你会怎么做?

  • 首先,我们先创建一个identity函数,将他的类型和返回值都固定为number类型,但是,该函数只能接收number类型的数据,如果我调用函数的时候传入字符串或者布尔值类型的值,此时就会报错。

    const identity: (value: number) => number = (value) => value
    console.log(identity(10)); // 输出:10,类型是number
    console.log(identity('Echo')); // 报错:类型“string”的参数不能赋值给类型“number”的参数
    
  • 为了让函数能够接收任意类型,可以将参数类型改为any,但是,这样就失去了 TS 的类型保护,会丢失一些信息:传入的类型与返回的类型应该是相同的。 我们传入一个数字,任何类型的值都有可能被返回。

    const identity: (value: any) => any = (value) => value
    console.log(identity('Echo'));    // 输出:Echo
    console.log(identity(26));        // 输出:26
    console.log(identity(true));      // 输出:true
    
  • 因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了类型变量,它是一种特殊的变量,只用于表示类型而不是值。

    function identity<T>(value: T): T {return value;
    }console.log(identity<string>('Echo'));   // 输出:Echo
    console.log(identity<number>(26));       // 输出:26
    console.log(identity<boolean>(true));    // 输出:true
    

    上述代码中,给identity添加了类型变量T。 T会捕获用户传入的类型(如:number、string)。 之后再次使用了T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。
    我们把这个版本的identity函数叫做泛型,因为它可以适用于多个类型不同于使用any,它不会丢失信息,像第一个例子那像保持准确性,传入数值类型并返回数值类型

三、泛型的使用

  • 首先,先定义一个函数

    function identity<T>(value: T): T {return value;
    }
    
  • 第一种使用方法:传入所有的参数,包含类型参数

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

    这里我们明确的指定了T是string类型,并做为一个参数传给函数,使用了<>括起来而不是()。

  • 第二种使用方法:利用类型推论 – 即编译器会根据传入的参数自动地帮助我们确定T的类型

    let output = identity("myString");
    

    注意我们 没必要使用尖括号(<>)来明确地传入类型;编译器可以查看myString的值,然后把T设置为它的类型。 类型推论帮助我们保持代码精简和高可读性。如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型,在一些复杂的情况下,这是可能出现的

四、泛型变量

看别人的ts代码会看到很多泛型T、E、V…之类的变量,为什么有的地方是T,有的地方是V。TypeScript在这方面没有强制要求,都是开发者们为了方便阅读理解,默认形成的一套规范。
常用的单个字母泛型变量:

T: 表示一般的泛型类型参数。
K: 表示对象中的键类型。
V: 表示对象中的值类型。
E: 表示数组或元组中的元素类型。
R: 表示函数的返回类型。
S, U, V, …: 表示额外的泛型类型参数。

五、多个类型参数

定义泛型的时候,可以一次定义多个类型参数:

function swap<T, U>(tuple: [T, U]): [U, T] {return [tuple[1], tuple[0]];
}console.log(swap(['Echo', 26])); // [26, 'Echo]

上例中,我们定义了一个 swap 函数,用来交换输入的元组。

六、泛型类

  • 泛型类(Generic Class) 是指在定义类时使用泛型类型参数的类它允许我们在类的属性、方法、构造函数以及实例化时使用泛型。
  • 泛型类使用(<>)括起泛型类型,跟在类名后面

下面是一个简单的泛型类的示例:

class Container<T> {private items: T[] = [];addItem(item: T) {this.items.push(item);}getItem(index: number): T {return this.items[index];}getItems(): T[] {return this.items;}
}const container = new Container<number>(); // 实例化一个泛型类,指定类型参数为 number
container.addItem(1);
container.addItem(2);
console.log(container.getItems()); // [1, 2]

类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以 类的静态属性不能使用这个泛型类型

七、泛型接口

  • 在接口名称的后面添加 <类型变量> ,那么,这个接口就变成了泛型接口

  • 接口的类型变量,对接口中所有其它成员可见,也就是 接口中所有成员都可以使用类型变量

  • 使用泛型接口时,需要显示指定具体的类型。

    下面是一个简单的泛型接口的示例:

    interface KeyValuePair<K, V> {key: K;value: V;
    }const pair1: KeyValuePair<number, string> = { key: 1, value: "one" };
    const pair2: KeyValuePair<string, boolean> = { key: "isEnabled", value: true };
    
  • 可以使用含有泛型的接口来定义函数的形状:

    interface CreateAtrrayFunc {<T>(length: number, value: T): Array<T>;
    }let createArray: CreateAtrrayFunc;createArray = function <T>(length: number, value: T): Array<T> {let result: T[] = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;
    };
    // createArray(3, 'x');
    console.log(createArray(3, 121)); // [ 121, 121, 121 ]
    
  • 进一步,我们可以把泛型参数提前到接口名上:

    interface CreateAtrrayFunc<T> {(length: number, value: T): Array<T>;
    }let createArray: CreateAtrrayFunc<number> = function <T>(length: number, value: T): Array<T> {let result: T[] = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;
    };console.log(createArray(3, 121));//[ 121, 121, 121 ]
    

    注意,此时在使用泛型接口的时候,需要定义泛型的类型

八、泛型参数默认类型

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

function createArray<T = string>(length: number, value: T): Array<T> {let result: T[] = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;
}
console.log(createArray(3, '121'));//[ '121', '121', '121' ]

九、泛型约束

  • 在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法

    function getLength<T>(arg: T): T {console.log(arg.length);// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.return arg;
    }
    

    上例中,泛型 T 不一定包含属性 length,所以编译的时候报错了。

  • 我们通过 extends 关键字进行类型约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束:

    interface ILength {length: number;
    }function getLength<T extends ILength>(value: T): T {console.log(value.length);return value;
    }getLength([1, 2, 3])                    // 正确,因为数组有 length 属性
    getLength('Echo') //                    // 正确,因为字符串有 length 属性
    getLength({ length: 10, name: 'Echo' }) // 正确,因为传入的参数有 length 舒心
    getLength(10)                           // 报错:类型“number”不能赋值给类型“ILength”的参数,因为数字不具有 length 属性
    
  • 多个类型参数之间也可以互相约束

    function copyFields<T extends U, U>(target: T, source: U): T {for (let id in source) {target[id] = (<T>source)[id];}return target;
    }let x = { a: 1, b: 2, c: 3, d: 4 };copyFields(x, { b: 10, d: 20 });
    

    上例中,我们使用了两个类型参数,其中要求 T 继承 U,这样就保证了 U 上不会出现 T 中不存在的字段。

参考文章:
TypeScript入门教程
TypeScript中文手册
【全文8W字】我是怎样从0开始学会🚀TypeScript🚀的
【2w+字笔记】前端的你用得上的TypeScript入门指北🧭

这篇关于TypeScript进阶知识之泛型(泛型的定义、为什么要使用泛型、泛型的使用、泛型变量、多个类型参数、泛型类、泛型接口、泛型参数默认类型、泛型约束)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/244790

相关文章

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

SpringValidation数据校验之约束注解与分组校验方式

《SpringValidation数据校验之约束注解与分组校验方式》本文将深入探讨SpringValidation的核心功能,帮助开发者掌握约束注解的使用技巧和分组校验的高级应用,从而构建更加健壮和可... 目录引言一、Spring Validation基础架构1.1 jsR-380标准与Spring整合1

Python虚拟环境终极(含PyCharm的使用教程)

《Python虚拟环境终极(含PyCharm的使用教程)》:本文主要介绍Python虚拟环境终极(含PyCharm的使用教程),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录一、为什么需要虚拟环境?二、虚拟环境创建方式对比三、命令行创建虚拟环境(venv)3.1 基础命令3

Python Transformer 库安装配置及使用方法

《PythonTransformer库安装配置及使用方法》HuggingFaceTransformers是自然语言处理(NLP)领域最流行的开源库之一,支持基于Transformer架构的预训练模... 目录python 中的 Transformer 库及使用方法一、库的概述二、安装与配置三、基础使用:Pi

关于pandas的read_csv方法使用解读

《关于pandas的read_csv方法使用解读》:本文主要介绍关于pandas的read_csv方法使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录pandas的read_csv方法解读read_csv中的参数基本参数通用解析参数空值处理相关参数时间处理相关

使用Node.js制作图片上传服务的详细教程

《使用Node.js制作图片上传服务的详细教程》在现代Web应用开发中,图片上传是一项常见且重要的功能,借助Node.js强大的生态系统,我们可以轻松搭建高效的图片上传服务,本文将深入探讨如何使用No... 目录准备工作搭建 Express 服务器配置 multer 进行图片上传处理图片上传请求完整代码示例

SpringBoot条件注解核心作用与使用场景详解

《SpringBoot条件注解核心作用与使用场景详解》SpringBoot的条件注解为开发者提供了强大的动态配置能力,理解其原理和适用场景是构建灵活、可扩展应用的关键,本文将系统梳理所有常用的条件注... 目录引言一、条件注解的核心机制二、SpringBoot内置条件注解详解1、@ConditionalOn

Python中使用正则表达式精准匹配IP地址的案例

《Python中使用正则表达式精准匹配IP地址的案例》Python的正则表达式(re模块)是完成这个任务的利器,但你知道怎么写才能准确匹配各种合法的IP地址吗,今天我们就来详细探讨这个问题,感兴趣的朋... 目录为什么需要IP正则表达式?IP地址的基本结构基础正则表达式写法精确匹配0-255的数字验证IP地

MySQL 中查询 VARCHAR 类型 JSON 数据的问题记录

《MySQL中查询VARCHAR类型JSON数据的问题记录》在数据库设计中,有时我们会将JSON数据存储在VARCHAR或TEXT类型字段中,本文将详细介绍如何在MySQL中有效查询存储为V... 目录一、问题背景二、mysql jsON 函数2.1 常用 JSON 函数三、查询示例3.1 基本查询3.2