likes
comments
collection
share

👀TypeScript防脱发级入门——扩展类型枚举

作者站长头像
站长
· 阅读数 9

嗨!大家好!我是法医,一只治疗系前端码猿🐒,与代码对话,倾听它们心底的呼声,期待着大家的点赞👍与关注➕。

扩展类型——枚举

在上一篇文章中聊到了很多TS基础类型,那为什么又出现了扩展类型枚举,我们都知道任何东西都不是平白无故就出现的,都是为了解决特定的问题。枚举只是扩展类型其中之一,比如说还有类型别名接口

那么枚举是什么意思呢?

📢 枚举就是把一卡车西瓜一个一个摆在摊子上的过程。

有什么作用?

📢 枚举通常用来约束某个变量的取值范围,当然不仅仅是变量,还有函数的参数或者函数的返回值,比如说约束变量sex性别的范围,不是男就是女了,不可能是不男不女吧😂

看过我上一篇文章的小伙伴也许会回忆到当时文章中也定义了变量gender性别取值范围,当时是用字面量配合联合类型处理的

举个栗子🌰:

//通过字面量配合联合类型也能达到约束变量的目的
let gender : "男" | "女";

字面量配合联合类型也能达到约束变量的目的,那么它和枚举有什么区别呢?不用想,肯定是遇到解决不了的问题,否则整个枚举出来多此一举没意义。接下来我会详细地介绍,你可以学到:

  • 枚举出现的契机是什么
  • 它解决了什么问题

一、字面量类型的问题

1. 在类型约束位置会产生重复代码,可以使用类型别名解决该问题

举个栗子🌰:

我们在代码中定义一个gender变量,类型约束为或者,gender只能赋值男或者女,其它值不行,接下来有个查询用户的函数searchUsers,这个函数是根据性别进行查询用户的,如下:

//定义一个gender变量,并且约束为男或者女
let gender : "男" | "女";

//gender可以赋值为男或者女
gender = "女";
gender = "男";

//根据性别查询函数
function searchUsers(g:"男" | "女") {
    
}

从代码中可以看到变量gender约束函数参数约束产生相同代码"男" | "女",看过前面的小伙伴也许已经按捺不住了,因为前面提到过一个类型别名,它是可以解决这个问题的,如下:

//定义一个类型别名
type Gender = "男" | "女";

//定义一个gender变量,并且约束为男或者女
let gender : Gender;

//gender可以赋值为男或者女
gender = "女";
gender = "男";

//根据性别查询函数
function searchUsers(g:Gender) {
    
}

所以在类型约束位置会产生重复代码,可以使用类型别名解决该问题。但是其它问题是没有办法通过类型别名来解决的

2. 逻辑含义和真实的值产生了混淆,会导致当修改真实值的时候,产生大量修改

举个栗子🌰:

//定义一个类型别名
type Gender = "男" | "女";

//定义一个gender变量,并且约束为男或者女
let gender : Gender;

//gender可以赋值为男或者女
gender = "女";
gender = "男";

//根据性别查询函数
function searchUsers(g:Gender) {
    
}

上面代码中定义了类型别名Gender并且约束为或者,在后续写代码过程中,肯定会给gender赋值男或女,我们可以想象在几万行代码中大量用到gender变量进行赋值,有一天没吃药的产品经理提了个需求:性别用男女约束太俗了,显得没有档次,换个美女帅哥,如下:

//定义一个类型别名
type Gender = "帅哥" | "美女";

//定义一个gender变量,并且约束为男或者女
let gender : Gender;

//gender可以赋值为男或者女
gender = "女";
gender = "男";

//根据性别查询函数
function searchUsers(g:Gender) {
    
}

换成美女帅哥后是不是要疯了,几十万行代码中大量使用了变量gender赋值为字面量,约束名一改,后面所有用到的地方都要跟着改,产生大量的修改,为什么会这样呢?根本原因是将逻辑含义和真实的值混淆在了一起,目前约束的美女和帅哥是真实的值,但不管是帅哥、美女,还是男、女都是一个含义,都表示生物学上的性别男和女,使用字面量类型的时候会导致真实的值和逻辑含义的值是对应的,但是在给变量赋值的时候只能赋值为真实的值,逻辑含义的值不变,但真实的值却变了,就产生了大量的修改,这个问题靠类型别名是没法解决的

3. 字面量类型不会进入到编译结果

字面量类型是不会参与编译,运行完成后会消失,如下:

 👀TypeScript防脱发级入门——扩展类型枚举

如果我们想在TS中动态读取变量中有哪些取值并显示在页面上,是做不到的,因为TS在编译的时候已经丢失了变量约束信息。

第二个和第三个问题想要解决就需要靠枚举

二、枚举的 “道”

1. 如何定义一个枚举?

//枚举字段表示性别有哪些取值
enum 枚举名 {
    枚举字段1:值1,
    枚举字段2:值2,
    枚举字段3:值3,
}
//以性别为例
enum Gender {
    male = "美女",
    female = "帅哥",
}

从上述枚举定义来看,控而已发现有两个值,一个枚举字段名,另一个就是,之所以会有两个值,就是解决逻辑含义和真实的值所产生的混淆,而在枚举中将逻辑含义和真实的值分开了,左边是逻辑含义值,右边是真实的值,有了枚举之后就不再需要类型别名了,对字面量类型出现问题的代码进行修改:

//定义一个类型别名
//type Gender = "帅哥" | "美女";

//换成枚举
enum Gender {
    male = "美女",
    female = "帅哥",
}


//定义一个gender变量,并且约束为男或者女
let gender : Gender;

//gender可以赋值为男或者女
//gender = "女";
//gender = "男";

//使用了枚举之后,就不能用真实的值进行赋值了
gender = Gender.male;
gender = Gender.female;

//根据性别查询函数
function searchUsers(g:Gender) {
    
}

如此就解决了字面量类型中第二个问题:逻辑含义和真实的值产生的混淆,一旦修改真实的值,造成后续大量代码的修改。使用了枚举之后将来直接修改真实的值就可以了,仅需要改一个地方即可,因为后续所有的代码都是用的是逻辑含义的值,这是不会变化的。

枚举另外一个好处就是,枚举会参与代码的编译,它会出现编译的结果中,枚举在JS中表现为对象,如下:

 👀TypeScript防脱发级入门——扩展类型枚举

由于TS中使用了中文,所以它会进行unicode编码,编译后的JS文件中,首先声明了一个变量Gender,然后将Gender传入函数,如果Gender没有值,那么赋值为一个对象,随后给对象中添加一条属性male,赋值为美女,以及female赋值为帅哥,因此可以发现枚举是会参与代码的编译,所以我们能在代码编译运行阶段动态的打印枚举中的属性值。如下:

 👀TypeScript防脱发级入门——扩展类型枚举

然而之前的类型别名是做不到的,因为类型别名在编译的时候会消失,而枚举则不会。

因此如果我们需要在运行的过程中使用某个取值范围的话,应该使用枚举,而且防止未来修改大量的代码,应当将逻辑含义跟真实的值分开,也应当使用枚举

📢 问题来了: 那我们该何时用类型别名?有何时用枚举呢?

👉 解答:

实际上不用纠结,只要是取值范围都可以用枚举,用它准没错😁

2. 枚举的规则

  • 枚举的值可以是字符串或数字

    字符串刚刚已经说过了,枚举性别用的就是字符串,我们把值为数字的称为数字枚举

  • 数字枚举的值会自动自增,只有数字有这个特点

    举个栗子🌰:

    定义了一个data枚举,是周一到星期天的字段值,当给Monday赋值为1时,其它值会根据上一个值依次自增,如果第一个值没有赋值,那么它会从0开始,

     👀TypeScript防脱发级入门——扩展类型枚举

  • 数字枚举的编译结果和字符串枚举编译的结果有差异

    这是数字枚举编译后的结果

     👀TypeScript防脱发级入门——扩展类型枚举

3. 枚举的最佳实践

  • 尽量不要在一个枚举中既出现字符串字段,又出现数字字段
  • 使用枚举时,尽量使用枚举字段的名称,而不使用真实的值,就是把逻辑的值和真实的值分开
  • 能用枚举就用枚举,使用类型别名,那两个问题逃不掉

三、枚举扩展知识——位枚举

位枚举也可以叫枚举位运算,这里的位枚举针对的是数字枚举,字符串枚举是不行的,这里举个栗子🌰来说明位运算,我们都知道一个文件有很多操作权限,可读可写可创建可删除,权限有对应的取值,这里是数字,不能超过这个范围,如下:

enum Permission{
    Read,
    Write,
    Create,
    Delete
}

以上就是一个文件的四个权限,但是目前有个问题,有的时候我们需要对这些权限进行组合,有的只能读和写不能创建和删除,也许我们会这样写:

enum Permission{
    Read,
    Write,
    Create,
    Delete,
    ReadAndWrite,
    WriteAndCreate,
    //...依次类推,太多了,写不完
}

这四个一组合有A44种排列组合方式,也就是24种结果,如果将来再加点或者删除点字段,那写的就更多了,所以说全写出来放在Permission中非常不方便,那么怎么处理呢?有一种非常巧妙的办法,先把字段分别赋值为1248,如下:

enum Permission{
    Read = 1,
    Write = 2,
    Create = 4,
    Delete = 8
}

我们可以先看下这些数字有什么特点,是不是后面的数字是前面一个的两倍,换句话说,1 = 2^02 = 2^14 = 2^28 = 2^4,它们全是2的n次方,如果换算成二进制的话,它们其中一位是1,其余是0,1的二进制是0001,2的二进制是0010,4的二进制是0100,8的二进制是1000,我们可以通过二进制某一位上是否有1来表示是否有这个权限,比如0001第四位上是1表示有读的权限,再比如:0011可以表示有读和写的权限,所以我们可以通过这些基本权限来组合新的权限

1.如何组合新的权限

比如说我们要组合的权限,可以这样:

enum Permission{
    Read = 1,  //0001
    Write = 2, //0010
    Create = 4,//0100
    Delete = 8 //1000
}

//1. 如何组合类型
//使用 或 运算
let rwP = Permission.Read | Permission.Write;

我们需要注意的是这里的|可不是TS中的联合类型哦,这里的叫或运算,它是位运算中其中之一

位运算: 指的是两个数字转换成二进制后用每一位进行的运算,位运算有很多种,|:或运算是其中之一

首先分别拿到的二进制:00010010,它俩进行或运算或运算的规则是用每一位进行比较,有一位是1,那么结果就是1,否则为0,所以最后结果是0011

0001
//或运算
0010

//最后结果是 0011

2.如何判断是否拥有某个权限

经过或运算的处理后,rwP会得到一个数字,将它作为目标值传入函数中与权限进行对比

enum Permission{
    Read = 1,  //0001
    Write = 2, //0010
    Create = 4,//0100
    Delete = 8 //1000
}

//1. 如何组合类型
//使用位运算
let rwP = Permission.Read | Permission.Write;

/**
 * 判断target里面包不包含p这个权限
 * @param target 目标值
 * @param p 某个权限
 */
function hasPermission(target:Permission,p:Permission) {
    return (target & p) === p
}
//判断rwP有没有“读”这个权限
let result = hasPermission(rwP,Permission.Read);
//判断rwP是否拥有Read权限
console.log(result);//返回true

其实判断是否拥有某个权限很简单,要判断是否有Read这个权限,只要判断它的二进制0001最后一位是不是等于1就行了。

我们再来看这段代码(target & p) === p,其中&叫做且运算,它也是位运算中一种

且运算:比较两个数的二进制,只要当两个数的二进制相同位置上都是1的时候才是1,反之为0。比方说00110010进行且运算后的值为0010,再与权限相比,相等则表示拥有这个权限,反之没有。

3.如何删除某个权限

enum Permission{
    Read = 1,  //0001
    Write = 2, //0010
    Create = 4,//0100
    Delete = 8 //1000
}

//1. 如何组合类型
//使用位运算
let rwP = Permission.Read | Permission.Write;

/**
 * 判断target里面包不包含p这个权限
 * @param target 目标值
 * @param p 某个权限
 */
function hasPermission(target:Permission,p:Permission) {
    return (target & p) === p
}
//判断rwP有没有“读”这个权限
let result = hasPermission(rwP,Permission.Read);

// 3. 如何删除某个权限

rwP = rwP ^ Permission.Write;
console.log(hasPermission(rwP,Permission.Write));//返回false表示清除了可写的权限

删除某个权限可以通过rwP = rwP ^ Permission.Write;重新赋值就可以了,这段代码中^表示异或

异或:比较两个数字的二进制,两者相同位置的数字最后结果取0,不同取1,比方说之前的权限是0011,要删除0010读的权限,最后结果是0001

如果将来我们遇到可选权限方面的场景可以使用位运算的方式进行处理,这种方式非常优雅,扩展性比较好

最后

很感谢小伙伴看到最后😘,如果您觉得这篇文章有帮助到您的的话不妨关注➕+点赞👍+收藏📌+评论📜,您的支持就是我更新的最大动力。