解密!TS的枚举与约束

关注“大前端私房菜”微信公众号,回复暗号【面试宝典】即可免费领取107页前端面试题。 

 枚举

认识枚举

在很多计算机语言中都有枚举的概念, 但是 JS 中是没有枚举这个概念的, 为了弥补这个缺憾 在 TS 加入了枚举类型

什么是枚举呢 ?

枚举( mei ju ) : 枚举的意思就是一一列举, 把所有情况都列举出来, 那么取值的时候, 只有这几个可以使用, 其他的都不行,计算机语言里面的枚举( enumerations ) : 把所有的常量放在一个集合内, 让若干个常量变成一组有关联的内容。

// 针对一个业务逻辑, 我需要频繁用到四个方向的字符串
const UP = 'up'
const RIGHT = 'right'
const DOWN = 'down'
const LEFT = 'left'

对于以上四个变量来说,我不管做任何逻辑, 我没办法限制你只能使用这四个变量中的一个

// 封装一个功能函数
function util(dir) {}

不管用什么方法, 你都没办法限制这个 dir 参数接收到的必须是上面列出的四个方向,这个时候, 我们就可以用到枚举了。

首先, 在 TS 中, 利用 enum 关键字创建一个枚举集合, 把我们需要的四个常量放进去

enum Direction {
  UP = 'up',
  RIGHT = 'right',
  DOWN = 'down',
  LEFT = 'left'
}

制作了一个 DIrection 枚举集合, 那么就可以用这个集合来对某些数据进行限制了

function util(dir: Direction) {}

这就约定了, dir 这个参数的值只能是 Direction 这个枚举集合里面的常量, 其他都不行

只要你写的不是 Direction 这个枚举内的内容都不行

数字枚举

数字枚举 : 枚举类型中的每一个常量都是数字,在 TS 中, 枚举内的每一个常量, 当你不设置值的时候, 默认就是 number 类型。

enum Pages {
    ONE,    // 0
    TWO,    // 1
    THREE   // 2
}

你在枚举内的常量, 第一个默认值是 0, 后面的依次 +1 递增,此时。

Pages.ONE => 0

Pages.TWO => 1

Pages.THREE => 2

我们也可以自己指定值

enum Pages {
    ONE = 10,    // 10
    TWO = 20,    // 20
    THREE = 30   // 30
}

这个时候枚举集合内的常量就是我们指定好的值,我们也可以指定部分值。

enum Pages {
    ONE = 10,    // 10
    TWO,         // 11
    THREE        // 12
}

指定常量后面的未指定常量, 就会按照 +1 的规则一次递增

enum Pages {
    ONE,         // 0
    TWO = 10,    // 10
    THREE        // 11
}
enum Pages {
    ONE,         // 0
    TWO = 10,    // 10
    THREE,       // 11
    FOUR = 30,   // 30
    FIVE         // 31
}

字符串枚举

字符串枚举 : 枚举集合中的每一个常量的值都是 string 类型,在 TS 内, 你必须要指定一个值, 才可能会出现 string 类型。

enum Direction {
  UP = 'up',
  RIGHT = 'right',
  DOWN = 'down',
  LEFT = 'left'
}

在 TS 中, 枚举常量和任何内容都是不一样的, 包括原始字符串

function util(dir: Direction) {}

这是因为, 在 TS 中, 枚举内的每一个常量都是一个独一无二的值,所以当你用枚举去限定一个数据的时候, 用的时候也只能用枚举内的值,这样也避免你因为手误出现的单词错误, 比如你会不会认为 'form' 和 'from' 是一个单词呢?

异构枚举

异构枚举 : 其实就是在一个枚举集合内同时混合了数字枚举和字符串枚举,但是你大概率是不会这样使用的, 因为我们作为一组数据的集合, 一般不会把数字和字符串混合在一起使用

enum Info {
  ONE,
  UP = 'up',
  TWO = 2,
  LEFT = 'left'
}

在这里有一个点需要注意,因为在枚举集合内, 当某一个 key 你没有设置值的时候, 会默认按照上一个的值 +1,所以如果前一个是 字符串枚举, 那么下一个必须要手动赋值, 不然会报错,如果前一个是 数字枚举, 那么下一个可以不必要手动赋值, 会按照上一个 +1 计算。

枚举合并

在 TS 内的枚举, 是支持合并的,多个枚举类型可以分开书写, 会在编译的时候自动合并。

enum Direction {
  UP = 'up',
  RIGHT = 'right',
  DOWN = 'down',
  LEFT = 'left'
}

enum Direction {
  TOP = 'top',
  BOTTOM = 'bottom'
}

function util(dir: Direction) {}

util(Direction.BOTTOM)
util(Direction.LEFT)

这里定义的两个枚举都叫做 Direction, 会在编译的时候自动放在一起, 不会出现冲突

反向映射

TS 内的数字枚举, 在编译的时候, 会同时将 key 和 value 分别颠倒编译一次

enum Pages {
    ONE,    // 0
    TWO,    // 1
    THREE   // 2
}

 以这个为例, 他是如何进行编译的呢

var Pages;
(function (Pages) {
    Pages[Enum["ONE"] = 0] = "ONE"
    Pages[Enum["TWO"] = 1] = "TWO"
    Pages[Enum["THREE"] = 2] = "THREE"
})(Pages || (Pages = {}));

编译完毕的结果

Pages = {
    ONE: 0,
    TWO: 1,
    THREE: 2,
    '0': 'ONE',
    '1': 'TWO',
    '2': 'THREE'
}

也就是说, 我们在 TS 内使用的时候, 如果是数字枚举,那么我们可以通过 key 得到对应的数字, 也可以通过对应的数字得到对应的 key

enum Pages {
    ONE,    // 0
    TWO,    // 1
    THREE   // 2
}

console.log(Pages.ONE)    // 0
console.log(Pages.TWO)    // 1
console.log(Pages.THREE)  // 2
console.log(Pages[0])     // 'ONE'
console.log(Pages[1])     // 'TWO'
console.log(Pages[2])     // 'THREE'

常量枚举

常量枚举, 是在枚举的基础上再加上 const 关键字来修饰,会在编译的时候, 把枚举内容删除, 只保留编译结果,并且对于数字枚举来说, 不在支持反向映射能力, 只能利用 key 来访问,非常量枚举。

enum Pages {
    ONE,    // 0
    TWO,    // 1
    THREE   // 2
}

console.log(Pages.ONE)
console.log(Pages.TWO)
console.log(Pages.THREE)

编译完毕的 js 文件

常量枚举

const enum Pages {
    ONE,    // 0
    TWO,    // 1
    THREE   // 2
}

console.log(Pages.ONE)
console.log(Pages.TWO)
console.log(Pages.THREE)

编译完毕的 js 文件

类型约束

在 TS 中, 还有一个很神奇的关键字, 叫做 type,type 又叫做类型别名有很多神奇的功能, 不仅能支持 interface 定义的对象结构, 还支持任何手写类型,先来看一个很简单的例子。

let n1: number | string | boolean
let n2: number | string | boolean
let n3: number | string | boolean

观察上面一段代码, 我们定义了 n1 和 n2 和 n3 三个变量,对于类型的限制都是 number 或者 string 或者 boolean,写起来的时候就非常麻烦,这个时候, 我们就可以使用 type 对其进行别名设置。

type Info = number | string | boolean
let n1: Info
let n2: Info
let n3: Info

这样一来, 我们的代码是不是变得简洁了起来,可能小伙伴们认为这个用的并不多, 但是 type 也不是只有这一个功能。

 type的常见使用

基本类型的别名

type n = number
let num: n = 100

这是一个非常基础的使用, 把 number 这个类型起了一个别名叫做 n,今后再用 n 来限制变量的时候, 其实就是在使用 number

基本类型联合

type i = number | string
let str: i = '千锋大前端'
str = 100

这就是联合类型, 那 number 或者 string 这个类型齐了一个别名叫做 i,我们再用 i 来限制变量的时候, 这个变量就被限制为了 number 或者 string。

对象类型

type User = { name: string, age: number }
let person: User = { name: '千锋大前端', age: 10 }

这就是对象类型, 和 interface 很像, 用处基本一致

对象联合类型

type User = { name: string, age: number }
type Person = User & { gender: boolean }
let person: Person = { name: '千锋大前端', age: 10, gender: true }

这就是对象联合类型, 和 interface 的 extends 继承很像

元组类型

type data = [ number, string ]
let info: data = [ 10, '千锋大前端' ]

函数类型

type func = (x: number, y: number) => number

常量限定

type color = 'yellow' | 'orange' | 'blue'
function util(c: color) {}
util('yellow')

这个 color 被限定为了几个值, 将来用 color 去约束一个变量的时候,这个变量只能接受这几个值, 这里和 enum 比较像了。

 type 和 interface的共同点

1. 都可以约束 对象 或者 函数 类型

interface

interface User { name: string; age: number }
interface Func { (x: number): number }

type

type User = { name: string; age: number }
type Func = (x: number) => number

我们看到, 两个定义方式略有区别, 但是后期用法基本一致

2. 扩展类型

interface 使用 extends 进行继承

interface Person {
    name: string
    age: number
}

// 使用 extends 关键字继承自 Person
interface Student extends Person {
    classRoom: number
}
let s: Student = { name: '千锋大前端', age: 10, classRoom: 1 }

type 使用 交叉(&) 来实现

type Person = {
    name: string
    age: number
}

// 使用 交叉(&) 
type Student = Person & {  
    classRoom: number
}
let s: Student = { name: '千锋大前端', age: 10, classRoom: 1 }

3. 联合类型

interface 使用 extends 继承 type

type Person = {
    name: string
    age: number
}

// 使用 extends 关键字继承自 Person
interface Student extends Person {
    classRoom: number
}
let s: Student = { name: '千锋大前端', age: 10, classRoom: 1 }

type 使用 交叉(&) 扩展 interface

interface Person {
    name: string
    age: number
}

// 使用 交叉(&) 
type Student = Person & {  
    classRoom: number
}
let s: Student = { name: '千锋大前端', age: 10, classRoom: 1 }

 type 和 interface的区别

1. interface 支持多次声明自动合并, type 不支持

interface User {
    name: string
    age: number
}
interface User {
    classRoom: string
}
/*
    真实的 User 接口
    {
        name: string
        age: number
        classRoom: string
    }
*/

type 如果声明重名标识符会报错

2. 对于 ES6 模块化语法的默认导出语法

interface 支持声明的同时进行默认导出

export default interface User {  
    name: string
    age: number
}

type 必须先声明, 在默认导出

type User = {
    name: string
    age: number
}
export default User

必须要先声明好, 在进行默认导出, 如果直接连写默认导出, 会报错

3. type 可以使用 typeof 关键字去获取某一数据类型

这里定义了一个 EleType 标识符, 会自动根据 typeof 关键字检测的 box 的类型限制

4. type 支持使用 in 关键字去遍历成映射类型

type names = 'firstName' | 'lastName' | 'AKA'
type nameType = {
    [key in names]: string
}
/*
    真实的 nameType 类型
    {
        firstName: string
        lastName: string
        AKA: string
    }
*/

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值