玩转typescript类型

如题所述

第1个回答  2024-09-04

欢迎各位多加指正

1.类型是什么

简单来说,类型就是为编程语言提供不同内容的抽象:

不同类型变量占据内存大小

不同类型变量可做的操作不同保证某种类型只允许做该类型的操作叫做类型安全。比如number类型可以进行加减乘除的操作,而boolean类型不可以做这些操作,但是js是一种弱类型语言,即使对boolean类型进行了加减乘除,也不会报错,同时为了保证数据的正确运行,会进行强制类型转换,这就可能导致程序莫名的bug出现。

const?num?=?1;const?b?=?true;const?res?=?num?+?b;?//?2

那么我们怎么避免这样的不可控错误,答案就是类型检查。

保证类型安全方式叫做类型检查,根据类型检查的时间可以分为两种:

动态类型:运行时进行检查。

静态类型:编译时进行检查。

两种类型各有优劣,动态类型语言比较灵活,但是有类型不安地隐患;静态类型增加了代码编写的难度,但很多bug可以在编译阶段检查出来,从而可以消除不安全隐患。

可以做隐式类型转换的语言,叫做弱类型,不允许隐式类型转换的语言,叫做强类型。

动态类型只适合简单场景,对于大型项目(尤其是多人合作的大型项目)却不太合适,因为动态类型没法做约束,代码中会隐藏大量的隐患。而静态类型可以保证类型安全,很好的保证代码的健壮性,减少bug。

1.1类型系统分类

静态语言都有自己的类型系统,从简单到复杂可以分为3中类型:

简单类型系统

简单类型系统可以支持定义number,boolean,string,以及class。同时编译器也保证编译阶段的类型检测,保证类型安全。那么这样的类型系统有什么缺点呢?就是太死板,比如我们定义一个支持float和int的函数,需要用多态实现:

int?add(int?a,?int?b)?{????return?a?+?b;}int?add(float?a,?float?b)?{????return?a?+?b;}

那么这样的缺点如何解决呢,我们很容想到,如果类型不是在定义的时候确定,而是在调用的时候确定就好了,这就是第二种类型系统——支持泛型的类型系统。

支持泛型的类型系统

支持泛型的类型系统:泛型的英文是GenericType,通用的类型,它可以代表任何一种类型,也叫做类型参数。这样的类型系统大大增加了语言的灵活性,比如上面的例子可以如下改写:

T?add<T>(T?a,?T?b){????return?a?+?b;}

泛型系统的类型不是在定义的时候确定的,而是在调用时候确定,同时保证类型可以被记一下来,java就是这样的类型系统。支持泛型的类型系统极大的增强了语言的灵活性。但是对于JavaScript来说,还是远远不够的,因为JavaScript太过灵活了。

为了满足JavaScript灵活性,就要有第三种类型系统——支持编程的类型系统。

支持编程的类型系统(图灵完备)

对传入的参数进行各种逻辑运算,最终产生新的类型,这就是可编程的类型系统。

JavaScript真的需要这么复杂的类型系统吗?答案是确定。因为JavaScript实在是太灵活了,对于Java来说,所有的对象都是new出来的,而JavaScript对象可以是new出来,也可以是字面量,这就需要复杂的类型系统来保证类型的完备,比如下面例子,在Java中是绝对不能实现:

function<T>(obj:?T,?key:?extends?keyof?T):T[key]?{????return?T[key];}

TypeScript的类型系统是图灵完备的,也就是能描述各种可计算逻辑。简单点来理解就是循环、条件等各种JS里面有的语法它都有,JS能写的逻辑它都能写。

那么下面就让我们开始TypeScript类型之旅吧。

2.基础类型

这一部分的类型也是JavaScript中的基本类型,但也增加了一些非常有用的类型如tuple和enum,虽然他们的本质是Array和object。

Boolean

boolean是很简单false和true的集合

let?isCompleted:?boolean?=?false;

Number

number是所有数字的集合,各个进制数字的写法也和JavaScript保持一致。

let?decimal:?number?=?6;let?hex:?number?=?0xf00d;let?binary:?number?=?0b1010;let?octal:?number?=?0o744;

String

在TypeScript中同样支持双引号和单引号的字符串。

let?color:?string?=?"blue";color?=?'red';

Array

TypeScript中数组的写法有两种。

//?Type[]let?list:?number[]?=?[1,?2,?3];

//?Array<Type>let?list:?Array<number>?=?[1,?2,?3];

而因为有可能用到JSX语法,第二种写法会和JSX语法冲突,为了保持一致性,所以推荐使用第一种。

数组类型也是一些变量的集合,例如:

number[]就是[number]、[number,number]、[number,number,number]...的集合,而其中的number则如上所说是所有数字的集合。

Tuple

元组类型是几种类型的数组形式的固定组合,如下:

//?Declare?a?tuple?typelet?x:?[string,?number];//?Initialize?itx?=?["hello",?10];?//?OK//?Initialize?it?incorrectlyx?=?[10,?"hello"];?//?Error

元组类型在我看来是数组类型的子类型,如上number[]就是一些元组的集合。

而[string,number]则是string|number[]的子类型。即string|number[]为number[]、string[]、[string,number...]...,其中就包括一个[string,number]。

Enum

enum这个操作符比较特殊,它的定位类似于var、let、const,用于声明变量,但它只支持特定结构的变量声明,而这个结构就是一个对象。它的用法如下:

int?add(int?a,?int?b)?{????return?a?+?b;}int?add(float?a,?float?b)?{????return?a?+?b;}0

它的本质就是构建了一个对象,对象中属性的值默认从0开始,依次加1。当然你也可以设置为其他的值。

int?add(int?a,?int?b)?{????return?a?+?b;}int?add(float?a,?float?b)?{????return?a?+?b;}1

Object

object是对象类型,即它是所有对象的集合。

2.1特殊类型

这里会介绍一下在TypeScript类型系统中的空值、bottomtype、toptype等。

Null&Undefined

null和undefined都是空值,但因为在JavaScript中typeof操作符的行为,未定义的变量和定义了但未赋值的变量都是undefined,所以推荐定义了变量之后不会即刻赋值时,设置空值为null,这样可以区分开typeof的行为,当然这样可能会引入另一个问题,即typeofnull为object但依旧推荐这样做,可以在判断空值时直接使用foo!=null。

Unknown

unknown是TypeScript中的toptype,即任何类型都是它的子类型,它是TypeScript中所有可选值的集合。

Never

never是TypeScript中的bottomtype,即它是任何类型的子类型,但在TypeScript中它有着其他的作用,比如,当尝试给一个never类型的变量赋值时,中断当前程序运行,并抛出异常。

Any

any是TypeScript中非常特殊的类型,它既是toptype,又是bottomtype,即任何类型都是它的子类型,它又是任何类型的子类型。是不是很矛盾?但它的价值就在这里,TypeScript目前还无法完美支持JavaScript的所有能力,any就相当于一个缓冲,就是当你要做的事情TypeScript当前的类型系统还不支持的时候,就用any告诉编译器,这个你还不懂,但它是对的,然后编译器会非常相信你,当遇到any的时候,不做任何的类型检查。

所以在使用any之前,你要用尽浑身解数,尝试用TypeScript当前支持的能力来完成你所要做的工作,但当你发现TypeScript无法做到的时候,你就可以使用any了。

3高级类型3.1操作符

在进入高级类型之前,我们先看几个操作符:

typeof

typeof在TypeScript中还可以用来返回一个变量的声明类型,如果不存在,则获取该类型的推论类型。

int?add(int?a,?int?b)?{????return?a?+?b;}int?add(float?a,?float?b)?{????return?a?+?b;}2

keyof

TypeScript允许我们遍历某种类型的属性,并通过keyof操作符提取其属性的名称,类似Object.keys方法。keyof操作符是在TypeScript2.1版本引入的,可以用于获取某种类型的所有键,其返回类型是联合类型

int?add(int?a,?int?b)?{????return?a?+?b;}int?add(float?a,?float?b)?{????return?a?+?b;}3

keyof可以结合typeof一起使用,用于获取变量声明类型的key值的联合类型。这也给了我们一种获取联合类型的方式

int?add(int?a,?int?b)?{????return?a?+?b;}int?add(float?a,?float?b)?{????return?a?+?b;}4

in

用于遍历类型的属性key值,一般和keyof联合使用:

int?add(int?a,?int?b)?{????return?a?+?b;}int?add(float?a,?float?b)?{????return?a?+?b;}5

extends

这里的extends只是在类型系统的中的使用方法,不包括class的继承。

extends在Typescript用法较多,我们一一剖析:

用于现在类型范围:

int?add(int?a,?int?b)?{????return?a?+?b;}int?add(float?a,?float?b)?{????return?a?+?b;}6

用于条件类型

typescript2.8引入了条件类型表达式,类似于三元运算符,在这种意义下,extends提供了判断语句:

int?add(int?a,?int?b)?{????return?a?+?b;}int?add(float?a,?float?b)?{????return?a?+?b;}7

infer

infer可以用来声明一个待推断的类型变量,简单来说,相当于JavaScript中的const。

int?add(int?a,?int?b)?{????return?a?+?b;}int?add(float?a,?float?b)?{????return?a?+?b;}8

下面让我们根据这些操作符来玩转Typescript的高级类型。

3.2模式匹配

模式匹配是我们使用Typescript最有用的特性之一,我们要实现我们要实现数组的增删改查,数据的提取,字符串的操作等,都需要用到这个特性。而Typescript实现模糊匹配的操作符主要是infer,用于声明局部变量。

infer操作符只能和extends配合使用

关于条件类型中infer的官方文档:InferringWithinConditionalTypes

那么这个属性怎么用呢,我们从针对字符串,数组,函数等操作来玩这个操作符。

3.2.1数组

First

首先我们从数组中抽取第一个元素

int?add(int?a,?int?b)?{????return?a?+?b;}int?add(float?a,?float?b)?{????return?a?+?b;}9

对数组进行模糊匹配,我们要抽出第一个元素类型,放到通过infer声明的局部变量中,后面的可以是任意类型,放到unknown[]中,然后把局部变量First返回。

End

同理我们可以抽取最后一个元素

T?add<T>(T?a,?T?b){????return?a?+?b;}0

Rest

既然能取首位,我们就能取剩余类型

T?add<T>(T?a,?T?b){????return?a?+?b;}1

试一试

3.2.2字符串

字符串类型也同样可以做模式匹配,匹配一个模式字符串,把需要提取的部分放到infer声明的局部变量里。

startWith

我们判断一个字符是否以某个字符开头:

T?add<T>(T?a,?T?b){????return?a?+?b;}2

trim

字符串可以做模糊匹配,当然也可以做trim

T?add<T>(T?a,?T?b){????return?a?+?b;}3

Replace

我们可以对字符进行trim操作,那我们就肯定可以做replace操作

T?add<T>(T?a,?T?b){????return?a?+?b;}4

3.2.2函数

当然我们也可以对函数进行模式匹配,比如提取参数,返回值类型

Parameters

获取函数的参数类型:

T?add<T>(T?a,?T?b){????return?a?+?b;}5

ReturnType

能获取参数,就能获取返回值

T?add<T>(T?a,?T?b){????return?a?+?b;}6

3.3重新构造

typescript支持三种可以声明任意类型的变量,type,infer,类型参数,但三种方式都不能对原始类型进行修改,如果我们想修改原始类型,就需要重新构造,产生新的类型。

3.3.1数组构造

针对数组我们可以做到增删改。

Push

T?add<T>(T?a,?T?b){????return?a?+?b;}7

Unshift

可以在后面添加,当然也可以在前面添加

T?add<T>(T?a,?T?b){????return?a?+?b;}8

3.3.2字符型

我们可以对数组进行增删改,那么我们可以对字符串有哪些操作呢?

Uppercase

首先,我们可以对字符串进行首字母大写的操作

T?add<T>(T?a,?T?b){????return?a?+?b;}9

CamelCase

既然我们可以转写首字母,当然我们也可以将下划线类型,转为驼峰

function<T>(obj:?T,?key:?extends?keyof?T):T[key]?{????return?T[key];}0

上面的例子只是转换了一个下划线,如果有多个下划线我们怎么修改呢,答案是递归调用,后面我们在玩递归时会再次做这样的转换。

DeleteStr

既然能做转换,我们肯定就能做删除

function<T>(obj:?T,?key:?extends?keyof?T):T[key]?{????return?T[key];}1

3.3.3函数操作

针对函数操作,我们可以修改返回值,添加删除参数,修改参数类型等。

AddParameters

首先我们针对函数添加参数类型:

function<T>(obj:?T,?key:?extends?keyof?T):T[key]?{????return?T[key];}2

ChangeReturnType

我们还可以修改返回值类型

function<T>(obj:?T,?key:?extends?keyof?T):T[key]?{????return?T[key];}3

4递归判断

我们都知道一门语言必不可少的功能就是循环判断,只要有完备的循环判断,就可以构建出复杂的程序出来,那么做为图灵完备类型的Typescript类型系统,必定也会提供这两个功能:

extends:可以做为判断条件

递归:递归模拟循环

前面我们已经用到很多以extends来模拟判断的语句,比如:

function<T>(obj:?T,?key:?extends?keyof?T):T[key]?{????return?T[key];}2

下面我们主要来玩递归,看看用递归我们能实现那些骚操作。

4.1递归复用

递归(英语:Recursion),又译为递回,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。递归一词还较常用于描述以自相似方法重复事物的过程。例如,当两面镜子相互之间近似平行时,镜中嵌套的图像是以无限递归的形式出现的。也可以理解为自我复制的过程。

上面是维基百科对递归的解释

4.1.1数组递归

Includes

判断数组是否包含某一项是在JS最常用的功能,在Typescript系统中也是可以实现的。

function<T>(obj:?T,?key:?extends?keyof?T):T[key]?{????return?T[key];}5

Unique

我们能判断是否包含,就能做到去重操作

function<T>(obj:?T,?key:?extends?keyof?T):T[key]?{????return?T[key];}6

ArrayCreate

因为在Typescrip类型系统中中是没有new的,所以我们想要构造一个数组就需要用到递归逐个添加元素:

function<T>(obj:?T,?key:?extends?keyof?T):T[key]?{????return?T[key];}7

4.1.2字符串的递归操作

CamelCaseAll

在前面我们完重新构造时写过一个下划线转驼峰的类型,但是当时我们转了一层,hello_world可以转换为helloWorld,但是对于想is_need_update转换时就不能支持,因为最终转换出来为isNeed_update,如果想完成这样的转换,需要做递归操作

function<T>(obj:?T,?key:?extends?keyof?T):T[key]?{????return?T[key];}8

ReplaceAll

我们前面写过Replace,下面写一个增强版

function<T>(obj:?T,?key:?extends?keyof?T):T[key]?{????return?T[key];}9

4.1.3对象递归

我们知道Typescript我们提供了一个高级类型Readonly,就是将所有的属性变为Readonly,我们来试试实现这个功能

let?isCompleted:?boolean?=?false;0

上面我们已经实现了,但是如果我们对象有嵌套:

let?isCompleted:?boolean?=?false;1

此时,只能对外层进行修改,但是不能对内层进行修改

let?isCompleted:?boolean?=?false;2

如果想做到深层修改,就需要对对象进行递归

let?isCompleted:?boolean?=?false;3

外面为什么要extendsany,是为了触发执行,因为在typescript中是在访问时才触发更新。

5.加减乘除

我们接下来玩typescript类型中最好玩的一部分,就是加减乘除,当然在typescript的类型系统中是没有办法做加减乘除的,那我们怎么实现呢,前面我们已经用了数组的length属性属性了

let?isCompleted:?boolean?=?false;4

那我们就可以用数组的length模拟加减乘除了。

首先我们看看加法怎么实现,前面我们实现过一个类型:ArrayCreate,就是用来创建数组,那么加法就好实现

let?isCompleted:?boolean?=?false;5

减法

减法的运算为:差=被减数-减数,转换为数组则为:差值部分=整体部分-减去部分,即整体部分=减去部分+差值部分(被减数=减数+差)

let?isCompleted:?boolean?=?false;6

乘法

乘法的运算为:m+n,转换为数组思路为n个长度为m的数组相接的新数组的长度。

let?isCompleted:?boolean?=?false;7

除法

除法的运算为:m/n,转换为数组思路为长度为m的数组由多少个长度为n的数组组成。

let?isCompleted:?boolean?=?false;8

文件参考

TypeScript类型体操通关秘籍

TypeScript类型体操指北

原文:https://juejin.cn/post/709910045

logo设计

创造品牌价值

¥500元起

APP开发

量身定制,源码交付

¥2000元起

商标注册

一个好品牌从商标开始

¥1480元起

公司注册

注册公司全程代办

¥0元起

    官方电话官方服务
      官方网站八戒财税知识产权八戒服务商企业需求数字市场

玩转TypeScript,就来挑战类型体操
FlingYP-Type-Challengesextends关键字在TS中,extends关键字代表着从一个类型扩展出另外一个新类型,这个新类型是原来这个类型的子类型(是扩展的意思,并非继承的意思)typeSome=AextendsB?C:D条件类型 keyof关键字typeTestKeyof=keyofT 简单的说:keyof关键字将对象T类型的属性的Key全部收集起来组成为一...

玩转typescript类型
unknown是TypeScript中的toptype,即任何类型都是它的子类型,它是TypeScript中所有可选值的集合。 Never never是TypeScript中的bottomtype,即它是任何类型的子类型,但在TypeScript中它有着其他的作用,比如,当尝试给一个never类型的变量赋值时,中断当前程序运行,并抛出异常。 Any any是TypeScript中非常特殊的类型,它既...

分享一些前端优质的掘金小册,学完技术感觉已经超神了
3. 《玩转CSS的艺术之美》4. 《从零开发H5可视化搭建项目》5. 《玩转你的VUE3:升级你的前端框架》6. 《微信小程序底层框架实现原理》7. 《React进阶实践指南》8. 《TypeScript类型体操通关秘籍》9. 《从0到1落地前端工程化》10. 《前端缓存技术与方案解析》11. 《uniapp从入门到进阶》12. 《...

web前端开发学习_掘金前端课程(小册)推荐
13. TypeScript 全面进阶指南深入探讨 TypeScript 的应用,提高代码的健壮性、可读性,提升开发效率和体验。作者:阿里巴巴前端开发工程师,热衷于分享和推广 TypeScript 相关知识。点击进入学习 14. 玩转 CSS 的艺术之美深入 CSS 技巧,提供大量骚操作示例,提升审美标准和艺术创作能力。作者:资深前端工程...

【IT】Node CLI 之玩转 Commander.js
Commander.js提供了丰富的API和配置选项,支持自动化帮助信息、错误处理、事件监听、自定义事件回调和属性的遗留选项等特性。此外,文档还介绍了与TypeScript、Node选项、调试子命令、命令处理函数、退出和输出重写等相关知识。在实践应用中,开发者需注意版本兼容性、选项冲突、命令重复注册等常见问题,并利用...

玩转TypeScript,就来挑战类型体操
Awaited<T>工具类型Awaited<T>是TS4.5新添加的内置工具类型。可以帮助我们返回Promise对象里的类型。https:\/\/www.typescriptlang.org\/docs\/handbook\/release-notes\/typescript-4-5.html 关于00189-easy-awaited第二种方法的理解关键在于Infer关键字的使用,Infer只能用于条件类型extendsInfer关键字 \/**?*...

相似回答
大家正在搜