0%

Learning TypeScript From Scratch(6)

Enums


Numeric enums

  • auto-incremented
  • start from 0 by default
1
2
3
4
5
6
7
8
9
10
11
12
13
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}

enum Direction {
Up = 1,
Down, // 2
Left, // 3
Right // 4
}

String enums

  • each member should be constant-initialized with string literal or another string enum member
  • give a meaningful and readable value when it’s in runtime circumstance

Heterogeneous enums

  • type of each enum member can be different, but it’s not advised
1
2
3
4
enum awkward {
No = 0,
Yes = "YES"
}

Two kinds of member: constant、computed

1
2
3
4
5
6
7
8
9
10
11
enum FileAccess {
// constant
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
WhatElse = Read * Write,
// computed
L = "123".length;
G = getSomeValue();
}

One single rule: don’t put the member without initializer behind the computed member like below:

1
2
3
4
enum E {
A = getSomeValue(),
B // Error, cuz enum member must have initializer.
}

Two features of literal enum members

Literal enum member is a subset of constant enum member, which could be:

  • with no initialized value
  • with value initialized to any string literal(e.g."foo")、any numeric literal(e.g.1, -100)

Feature One: the enum members also become types as well, with which we can say that certain members can only have the value of an enum member.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum ShapeKind {
Circle,
Square
}

interface Circle {
kind: ShapeKind.Circle;
radius: number;
}

interface Square {
kind: ShapeKind.Square;
sideLength: number;
}

let c: Circle = {
kind: ShapeKind.Square, // Error, cuz ShapeKind.Square is not assignable to type ShapeKind.Circle
radius: 100
};

Feature Two: the enum types become a union of each enum member, with which the type system can know the exact set of values that exist in the enum, and can catch some simple bugs.

1
2
3
4
5
6
7
8
9
enum E {
Foo,
Bar
}

function func(e: E) {
// Error: This condition will always return 'true' since the types 'E.Foo' and 'E.Bar' have no overlap.
if(e !== E.Foo || e !== E.Bar) { ... }
}

Enums at runtime

Enums are real objects that exist at runtime.

1
2
3
4
5
6
7
8
9
10
11
enum E {
X,
Y,
Z
}

function getX(obj: { X: number }) {
return obj.X;
}

getX(E); // OK, cuz E has a property named X which is a number

Enums at compile time

I’m a little bit confused about this…maybe just write it down first.
Even though Enums are real objects that exist at runtime, the keyof keyword works differently than you might expect for typical objects. Instead, use keyof typeof to get a Type that represents all Enum keys as strings.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum LogLevel {
ERROR,
WARN,
INFO,
DEBUG,
}

/**
* This is equivalent to:
* type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
*/
type LogLevelStrings = keyof typeof LogLevel;

function printImportant(key: LogLevelStrings, message: string) {
const num = LogLevel[key];
if (num <= LogLevel.WARN) {
console.log("Log level key is:", key);
console.log("Log level value is:", num);
console.log("Log level message is:", message);
}
}
printImportant("ERROR", "This is a message");

Reverse mappings

  • only for numeric enums, not string enums
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Original
enum JustAEnum {
A,
B
}

// Javascript that Typescript compiles to
"use strict";
var JustAEnum;
(function (JustAEnum) {
JustAEnum[JustAEnum["A"] = 0] = "A";
JustAEnum[JustAEnum["B"] = 1] = "B";
})(JustAEnum || (JustAEnum = {}));

// So the following would work
let a = JustAEnum.A; // 0
let nameOfA = JustAEnum[a]; // "A"

Const enums

  • less cost when using const
  • can only use constant enum expressions, not computed enum expressions
  • itself is completely removed during compilation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Original
const enum Direction {
Up,
Down,
Left,
Right
}

let directions = [
Direction.Up,
Direction.Down,
Direction.Left,
Direction.Right,
];

// Javascript that Typescript compiles to
"use strict";
let directions = [
0 /* Up */,
1 /* Down */,
2 /* Left */,
3 /* Right */,
];

Ambient enums

  • ambient means 外部
  • are used to describe the shape of already existing enum types
  • difference between ambient and non-ambient
    • ambient: members without initializer are considered constant
    • non-ambient: members without initializer are considered computed
1
2
3
4
5
declare enum E {
A = 1,
B,
C = 2
}