首页 前端 TypeScript 正文

玩转typescript 类型

欢迎各位多加指正

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;
}


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

  • 支持泛型的类型系统

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

    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 中的基本类型,但也增加了一些非常有用的类型如tupleenum,虽然他们的本质是Arrayobject

Boolean

boolean是很简单falsetrue 的集合

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 type
let x: [string, number];
// Initialize it
x = ["hello", 10]; // OK
// Initialize it incorrectly
x = [10, "hello"]; // Error


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

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

Enum

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

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 类型系统中的空值、bottom type、top type等。

Null & Undefined

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

Unknown

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

Never

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

Any

any是 TypeScript 中非常特殊的类型,它既是 top type,又是 bottom type,即任何类型都是它的子类型,它又是任何类型的子类型。是不是很矛盾?但它的价值就在这里,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操作符是在TypeScript 2.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


  • 用于条件类型

typescript 2.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 的官方文档:Inferring Within Conditional Types

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

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


外面为什么要extends any,是为了触发执行,因为在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/7099100459213783077

打赏
海报

本文转载自互联网,旨在分享有价值的内容,文章如有侵权请联系删除,部分文章如未署名作者来源请联系我们及时备注,感谢您的支持。

转载请注明本文地址:https://www.shouxicto.com/article/4879.html

相关推荐

详解TypeScript中的泛型

详解TypeScript中的泛型

欢迎各位多加指正1. 类型是什么简单来说,类型就是为编程语言提供不同内容的抽象:不同类型变量占据内存大小不同类型变量可做的操作不同 保证...

TypeScript 2022.08.20 0 1086

TypeScript中你可能会忽略的细节

TypeScript中你可能会忽略的细节

欢迎各位多加指正1. 类型是什么简单来说,类型就是为编程语言提供不同内容的抽象:不同类型变量占据内存大小不同类型变量可做的操作不同 保证...

TypeScript 2022.06.21 0 1156

获取链表中倒数第K个节点

获取链表中倒数第K个节点

欢迎各位多加指正1. 类型是什么简单来说,类型就是为编程语言提供不同内容的抽象:不同类型变量占据内存大小不同类型变量可做的操作不同 保证...

TypeScript 2022.06.01 0 1022

TypeScript 泛型从新手到入门

TypeScript 泛型从新手到入门

欢迎各位多加指正1. 类型是什么简单来说,类型就是为编程语言提供不同内容的抽象:不同类型变量占据内存大小不同类型变量可做的操作不同 保证...

TypeScript 2022.05.31 0 1028

发布评论

ainiaobaibaibaibaobaobeishangbishibizuichiguachijingchongjingdahaqiandaliandangaodw_dogedw_erhadw_miaodw_tuzidw_xiongmaodw_zhutouganbeigeiliguiguolaiguzhanghahahahashoushihaixiuhanheixianhenghorse2huaixiaohuatonghuaxinhufenjiayoujiyankeaikeliankouzhaokukuloukunkuxiaolandelinileimuliwulxhainiolxhlikelxhqiuguanzhulxhtouxiaolxhwahahalxhzanningwennonuokpinganqianqiaoqinqinquantouruoshayanshengbingshiwangshuaishuijiaosikaostar0star2star3taikaixintanshoutianpingtouxiaotuwabiweifengweiquweiwuweixiaowenhaowoshouwuxiangjixianhuaxiaoerbuyuxiaokuxiaoxinxinxinxinsuixixixuyeyinxianyinyueyouhenghengyuebingyueliangyunzanzhajizhongguozanzhoumazhuakuangzuohenghengzuoyi
支付宝
微信
赞助本站