天天看點

TypeScript的枚舉與類型限制

作者:陸榮濤
TypeScript的枚舉與類型限制

● 上一章我們講了 TS 的接口

● 這一章, 我們就來聊一聊 TS 的枚舉和限制

枚舉

認識枚舉

● 在很多計算機語言中都有枚舉的概念, 但是 JS 中是沒有枚舉這個概念的, 為了彌補這個缺憾 在 TS 加入了枚舉類型

● 什麼是枚舉呢 ?

枚舉( mei ju ) : 枚舉的意思就是一一列舉, 把所有情況都列舉出來, 那麼取值的時候, 隻有這幾個可以使用, 其他的都不行

計算機語言裡面的枚舉( enumerations ) : 把所有的常量放在一個集合内, 讓若幹個常量變成一組有關聯的内容

TypeScript的枚舉與類型限制
// 針對一個業務邏輯, 我需要頻繁用到四個方向的字元串
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 這個枚舉集合裡面的常量, 其他都不行

TypeScript的枚舉與類型限制

● 隻要你寫的不是 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) {}           
TypeScript的枚舉與類型限制

● 這是因為, 在 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 檔案

TypeScript的枚舉與類型限制

● 常量枚舉

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

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

○ 編譯完畢的 js 檔案

TypeScript的枚舉與類型限制

類型限制

● 在 TS 中, 還有一個很神奇的關鍵字, 叫做 type

● type 又叫做類型别名有很多神奇的功能, 不僅能支援 interface 定義的對象結構, 還支援任何手寫類型

● 先來看一個很簡單的例子

TypeScript的枚舉與類型限制
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 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 如果聲明重名辨別符會報錯

TypeScript的枚舉與類型限制

2. 對于 ES6 子產品化文法的預設導出文法

○ interface 支援聲明的同時進行預設導出

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

○ type 必須先聲明, 在預設導出

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

○ 必須要先聲明好, 在進行預設導出, 如果直接連寫預設導出, 會報錯

TypeScript的枚舉與類型限制

3. type 可以使用 typeof 關鍵字去擷取某一資料類型

let box = document.querySelector('.box')
type EleType = typeof box           

○ 這裡定義了一個 EleType 辨別符, 會自動根據 typeof 關鍵字檢測的 box 的類型限制

4. type 支援使用 in 關鍵字去周遊成映射類型

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