如何通俗的解释TypeScript 泛型

概述

在 TypeScript 中我们会使用泛型来对函数的相关类型进行约束。这里的函数,同时包含 class 的构造函数,因此,一个类的声明部分,也可以使用泛型。那么,究竟什么是泛型?如果通俗的理解泛型呢?

什么是泛型

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

通俗的解释,泛型是类型系统中的“参数”,主要作用是为了类型的重用。从上面定义可以看出,它只会用在函数、接口和类中。它和js程序中的函数参数是两个层面的事物(虽然意义是相同的),因为 typescript 是静态类型系统,是在js进行编译时进行类型检查的系统,因此,泛型这种参数,实际上是在编译过程中的运行时使用。之所以称它为“参数”,是因为它具备和函数参数一模一样的特性。

function increse(param) {
  // ...
}

而类型系统中,我们如此使用泛型:

function increase<T>(param: T): T {
  //...
}

当 param 为一个类型时,T 被赋值为这个类型,在返回值中,T 即为该类型从而进行类型检查。

编译系统

要知道 typescript 本身的类型系统也需要编程,只不过它的编程方式很奇怪,你需要在它的程序代码中穿插 js代码(在 ts 代码中穿插 js 代码这个说法很怪,因为我们直观的感觉是在 js 代码中夹杂了 ts 代码)。

编程中,最重要的一种形式就是函数。在 typescript 的类型编程中,你看到函数了吗?没有。这是因为,有泛型的地方就有函数,只是函数的形式被 js 代码给割裂了。typescript 需要进行编译后得到最终产物。编译过程中要做两件事,一是在内存中运行类型编程的代码,从而形成类型检查体系,也就是说,我们能够对 js 代码进行类型检查,首先是 typescript 编译器运行 ts 编程代码后得到了一个运行时的检查系统本文来自否子戈的播客,运行这个系统,从而对穿插在其中的 js 代码进行类型断言;二是输出 js,输出过程中,编译系统已经运行完了类型编程的代码,就像php代码中 echo js 代码一样,php代码已经运行了,显示出来的是 js 代码。

从这个角度看 typescript,你或许更能理解为什么说它是JavaScript的超集,为什么它的编译结果是 js。

通俗的理解泛型

既然我们理解了 ts 编译系统的逻辑,那么我们就可以把类型的编程和 js 本身的业务编程在情感上区分开。我们所讲的“泛型”,只存在于类型编程的部分,这部分代码是 ts 的编译运行时代码。

我们来看下一个简单的例子:

function increase<T>(param: T): T {
  //...
}

这段代码,如果我们把 js 代码区分开,然后用类型描述文本来表示会是怎样?

// 声明函数 @type,参数为 T,返回结果为 (T): T
@type = T => (T): T

// 运行函数得到一个类型 F,即类型为 (number): number
@F = @type(number)

// 要求 increase 这个函数符合 F 这种类型,也就是参数为 number,返回值也为 number
@@F
function increase(param) {
  // ...
}
@@end

实际上没有 @@F 这种语法,是我编造出来的,目的是让你可以从另一个角度去看类型系统。

当我们理解泛型是一种“参数”之后,我们可能会问:类型系统的函数在哪里?对于 js 函数而言,你可以很容易指出函数声明语句和参数,但是 ts 中,这个部分是隐藏起来的。不过,我们可以在一些特定结构中,比较容易看到类型函数的影子:

// 声明一个泛型接口,这个写法,像极了声明一个函数,我们用描述语言来形容 @type = T => (T): T
interface GenericIdentityFn<T> {
    (arg: T): T;
}

// 这个写法,有点像一个闭包函数,在声明函数后,立即运行这个函数,描述语言:@@[T => (T): T](any)
function identity<T>(arg: T): T {
    return arg;
}

// 使用泛型接口,像极了调用一个函数,我们用描述语言来形容 @type(number)
let myIdentity: GenericIdentityFn<number> = identity;

上面这一整段代码,我们用描述文本重写一遍:

@GenericIdentityFn = T => (T): T

@@[T => (T): T](any)
function identify(arg) {
  return arg
}
@@end

@@GenericIdentityFn(number)
let myIdentity = identity
@@end

我们在类型系统中声明了两个函数,分别是 @GenericIdentityFn 和 @some(匿名函数 @[T => (T): T])。虽然是两个函数,但是实际上,它们的是一模一样的,因为 typescript 是结构类型,也就是在类型检查的时候只判断结构上的每个节点类型是否相同,而不是必须保持类型变量本身的指针相同。@GenericIdentityFn 和 @some 这两个函数分别被调用,用来修饰 identify 和 myIdentify,在调用的时候,接收的参数不同,所以导致最终的类型检查规则是不同的,identify 只要保证参数和返回值的类型相同,至于具体什么类型,any。而 myIdentify 除了保证参数返回值类型相同外,还要求类型必须是 number。

泛型类

除了泛型接口,class 类也可以泛型化,即“泛型类”,借助泛型类,我们来探究一下泛型的声明和使用的步骤。

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();

前文泛型接口因为只是为了约束函数的类型,所以写的很像函数,实际上,我们可以用描述语言重新描述一个泛型接口和泛型类。上面的红色部分,我们用描述语言来描述:

@GenericNumber = T => class {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

@GenericNumber 这个函数,以 T 为参数,返回一个 class,在 @type 函数体内多次用到了参数 T。

@GenericIdentityFn = T => interface {
  (arg: T): T;
}

我们重新描述了前面的 interface GenericIdentityFn,这样我们就可以在接口中增加其他的方法。

可以注意到,即使 typescript 内置的基础类型,例如 Array,被声明为泛型接口、泛型类之后,这些接口和类在使用时必须通过<>传入参数,本质上,因为它们都是函数,只是返回值不同。

其他泛型使用的通俗解释

接下来我们要再描述一个复杂的类型:

class Animal {
    numLegs: number;
}

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

我们姑且不去看 new() 的部分,我们看尖括号中的 extends语法,这里应该怎么理解呢?实际上,我们面对的问题是,在编译时,<A extends Animal> 尖括号中的内容是什么时候运行的,是之前,还是之间?

// 到底是
@type = (A extends Animal) => (new() => A): A
@type(T)
// 还是
@type = A => (new() => A): A
@type(T extends Animal)复

因为 typescript 是静态类型系统,Animal 是不变的类,因此,可以推测其实在类的创建之前,尖括号的内容已经被运行了。

@type = (A extends Animal) => (new() => A): A

也就是说,要使用 @type(T) 产生类型,首先 T 要满足 Animal 的结构,然后才能得到需要的类型,如果 T 已经不满足 Animal 类的结构了,那么编译器会直接报错,而这个报错,不是类型检查阶段,而是在类型系统的创建阶段,也就是 ts 代码的运行阶段。这种情况被称为“泛型约束”。

另外,类似 <A,B> 这样的语法其实和函数参数一致。

@type = (A, B) => (A|B): SomeType

我们再来看 ts 内置的基础类型:Array<number>

@Array = any => any[]

结语

Typescript 中的泛型,实际上就是类型的生成函数的参数。本文的内容全部为凭空想象,仅适用于对 ts 进行理解时的思路开拓,不适用于真实编程,特此声明。

以上就是如何通俗的解释TypeScript 泛型的详细内容,更多关于TypeScript泛型的资料请关注我们其它相关文章!

(0)

相关推荐

  • 你不知道的 TypeScript 高级类型(小结)

    前言 对于有 JavaScript 基础的同学来说,入门 TypeScript 其实很容易,只需要简单掌握其基础的类型系统就可以逐步将 JS 应用过渡到 TS 应用. // js const double = (num) => 2 * num // ts const double = (num: number): number => 2 * num 然而,当应用越来越复杂,我们很容易把一些变量设置为 any 类型,TypeScript 写着写着也就成了 AnyScript.为了让大家能更加深入

  • TypeScript 运行时类型检查补充工具

    TypeScript是静态类型系统,在编译时做类型检查.一般而言,如果项目所用到的所有库.模块都是基于ts的,那么静态类型已经可以避免大部分编程层面的类型问题.不过,在一些场景下来,单纯静态类型是无法解决问题的,部分数据是动态传入到系统中的,主要包含场景如下: 第三方数据源(接口API.本地持久化存储.postMessage等) 第三方调用者传参 全局状态变更 当然,还有其他可能,总之,单纯靠静态类型检查,无法解决运行时类型问题.因此,我写了tyshemo这个工具.它可以帮助我们完成运行时的类型

  • TypeScript 引用资源文件后提示找不到的异常处理技巧

    在tsx中引用图片,在文件文本编辑器中提示错误引用: typescript无法识别非代码文件(js是可以的).如果需要在ts中识别此文件资源,可以先声明文件类型. 新建一个ts文件,比如global.d.ts(.d.ts是typescript declaration file的简称),并放在主要代码文件夹下. 在ts文件中,添加各种文件类型的声明,比如: declare module '*.svg' { interface Svg { content: string; id: string; v

  • 7个好用的TypeScript新功能

    1. 可选链 从 v3.7 可用 这是当你尝试访问嵌套数据时的一个痛点,嵌套数据越多,代码就会变得越繁琐. 在下面的例子中,要访问address,你必须遍历data.customer.address,而且data或customer有可能是undefined,所以通常使用&&运算符或类似例子中的技巧遍历检查每个层次的定义. 现在你可以用.?运算符来选择性地对数据访问.通过这种方式,如果存在尚未定义的父级对象,则会在链中的任何位置返回未定义,而不是在运行时崩溃. // v3.7 以前 if (

  • typescript配置alias的详细步骤

    1 安装依赖 npm install --save-dev babel-plugin-module-resolver # yarn add babel-plugin-module-resolver --dev 根目录新增.babelrc文件 参考以下内容按您项目中的需要去修改 { "presets": ["next/babel"], "plugins": [ [ "module-resolver", { "alias

  • 详解TypeScript中的类型保护

    概述 在 TypeScript 中使用联合类型时,往往会碰到这种尴尬的情况: interface Bird { // 独有方法 fly(); // 共有方法 layEggs(); } interface Fish { // 独有方法 swim(); // 共有方法 layEggs(); } function getSmallPet(): Fish | Bird { // ... } let pet = getSmallPet(); pet.layEggs(); // 正常 pet.swim();

  • TypeScript泛型参数默认类型和新的strict编译选项

    概述 TypeScript 2.3 增加了对声明泛型参数默认类型的支持,允许为泛型类型中的类型参数指定默认类型. 接下来看看如何通过泛型参数默认将以下react组件从js(和jsX)迁移到 TypeScript (和TSX): class Greeting extends react.Component { render() { return <span>Hello, {this.props.name}!</span>; } } 为组件类创建类型定义 咱们先从为Component类

  • 前端深入理解Typescript泛型概念

    首先介绍一下泛性的概念 泛型程序设计(generic programming)是程序设计语言的一种风格或范式.泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型. 泛型是指在定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性. 先举一个简单的例子 假设我们定义一个函数,它可以接收一个number类型做为参数,并且返回一个number类型. function genericDemo(data: number):

  • 一篇文章带你使用Typescript封装一个Vue组件(简单易懂)

    一.搭建项目以及初始化配置 vue create ts_vue_btn 这里使用了vue CLI3自定义选择的服务,我选择了ts.stylus等工具.然后创建完项目之后,进入项目.使用快捷命令code .进入Vs code编辑器(如果没有code .,需要将编辑器的bin文件目录地址放到环境变量的path中).然后,我进入编辑器之后,进入设置工作区,随便设置一个参数,这里比如推荐设置字号,点下.这里是为了生成.vscode文件夹,里面有个json文件. 我们在开发项目的时候,项目文件夹内的文件很

  • 如何通俗的解释TypeScript 泛型

    概述 在 TypeScript 中我们会使用泛型来对函数的相关类型进行约束.这里的函数,同时包含 class 的构造函数,因此,一个类的声明部分,也可以使用泛型.那么,究竟什么是泛型?如果通俗的理解泛型呢? 什么是泛型 泛型(Generics)是指在定义函数.接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性. 通俗的解释,泛型是类型系统中的"参数",主要作用是为了类型的重用.从上面定义可以看出,它只会用在函数.接口和类中.它和js程序中的函数参数是两个层面的事物

  • TypeScript 泛型的使用

    目录 1.简单的使用 2.在函数中使用泛型 3.在类中使用泛型 4.在泛型约束中使用类型参数 前言: 在JavaScript中,封装一个API可以具有多种用途,因为其实弱类型语言,但是就因为是弱类型可以最终得到的结果并不是我们想要的. TypeScript的出现正好中解决了这个问题,但是考虑到API的复用时,TypeScript又显得不是这么的灵活.这个时候可以使用any类型来解决不灵活的问题,但是又回到JavaScript中的问题,得到最终的结果可能不是预期的那个样子. 为了解决这种情况,Ty

  • TypeScript 泛型重载函数的使用方式

    目录 前言 TypeScript 的运行环境 1. ts-node 2. tsc TypeScript 中的函数重载 简单的排序算法 1. 快速排序 2. 中文排序 3. 字符串自排序 4. 通过泛型整合几种排序 5. 使用函数重载完善排序功能 总结 前言 使用 TypeScript 进行开发也已经有段日子了,虽然最开始接触后以为这不就和 Java 一样一样的么,然而越深入了解越发现还真不一样~不过有些概念来说是相通的,比如泛型. Java 里也有函数重载,但是和 TS 差别还是挺大的,那就通过

  • typeScript 泛型使用和泛型接口结合

    目录 1.泛型是啥? 2.泛型类型 3.泛型接口 4.泛型类 5.泛型约束 6.泛型参数默认类型 7.泛型条件类型 typeScript 中新增的泛型概念.泛型使用.泛型与接口结合: 在实际应用中可能会遇到求最小值的问题,比如求数组中的最小值. 在 ts 中的就需要写两种方式,一种针对 number,另外一种针对字符串. 这样写不利于代码重用,项目较大时,性能较差,同时工作效率也低,所以在 ts 中引入了泛型概念. function getMin1(arr:number[]):number {

  • TypeScript泛型约束条件示例详解

    目录 什么是泛型 泛型的应用场景 泛型约束(限制条件) 泛型函数调用指定类型 总结 什么是泛型 两个值之间存在的对应关系,就可以用泛型来解决 泛型的应用场景 当一个函数的返回值的类型需要与此函数的参数类型想关联的时候,就需要使用泛型 例如 //约定此函数调用时必须传入一个数组,并返回数组第一项 function arrFn <T> (arr: T[]) :T|undefined { return arr[0] } const n = arrFn([1,2]) //number类型 const

  • TypeScript 泛型推断实现示例详解

    目录 前言 基础类型准备 最终使用的方式 基于Interface的实现 (失败了) 所有内容都基于type 实现 完整Demo 结束语 前言 最近做东西都在用ts,有时候写比较复杂的功能,如果不熟悉,类型写起来还是挺麻烦的.有这样一个功能,在这里,我们就不以我们现有的业务来举例了,我们还是已Animal举例,来说明场景.通过一个工厂来创建不同的动物实例.在这里我们借助泛型来实现类型的约束和动态推到指定类型. 基础类型准备 用一个枚举来定义Animal的类型 enum EAnimalType {

  • 关于对TypeScript泛型参数的默认值理解

    目录 泛型简介 举个 举个 泛型参数的默认值——函数重载 泛型参数的默认值——正文 参考 泛型简介 软件工程中,我们不仅要创建一致的定义良好的 API,同时也要考虑可重用性. 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能. 在像C# 和 Java 这样的语言中,可以使用 泛型 来创建可重用的组件,一个组件可以支持多种类型的数据. 这样用户就可以以自己的数据类型来使用组件. 举个 举个最简单的例子来理解泛型 function getVal(

  • 单口NAT的超级详细通俗的解释

    前些天,在网上看到些关于单口nat的配置,发现此配置全部出于一人之手,而且注释的很不明白,cisco官方站上也没有说的很明白,我现在给大家详细解释一下他的原理! 以下对照此帖讲解,可能不太方便,见谅! 首先,什么是单口nat,各位,不知道你们遇到过这种情况没有,就是有一条adsl线路,但是每台机器只有一块网卡,想实现所有机器共享上网,你们说怎么办呢?^_^,可以花5块钱再买块网卡,这当然简单了,但是我们做技术的就要想想其他方法,呵呵呵.挑战一下自己.单口nat说白了就是路由上就一个口,还必须要做

随机推荐