TypeScript enum антипаттерн
В книгах по TypeScript рецептам, говорится, что не стоит использовать enum, а лучше использовать объявление type.
Рассмотрим пример, правильного объявления перечисления:
const DIRECTIONS = {
UP: 'UP',
DOWN: 'DOWN'
} as const;type DIRECTIONS = keyof typeof DIRECTIONS;
- В первой строке, объявляем объект с
readonly
свойствами, что дает нам уверенность в том, что никто не сможет изменить значениеDIRECTIONS.UP
на какое либо другое значение и тоже самое проDIRECTIONS.DOWN
, при работе с кодом на TypeScript. - Во второй строке, получаем обычное объявление type, но без дублирования кода. Мы могли бы написать:
type DIRECTIONS = 'UP' | 'DOWN'
но лучше, использовать возможности TypeScript, для соблюдения принципа DRY — Don't Repeat Yourself
. И в этом нам помогает строка:
type DIRECTIONS = keyof typeof DIRECTIONS;
// в итоге получаем type DIRECTIONS = 'UP' | 'DOWN'
Применение данного способа следующее:
const someDirection1: DIRECTIONS = "UP";const someDirection2: DIRECTIONS = "DOWN";const someDirection3: DIRECTIONS = DIRECTIONS.UP;
Плюс к возможности подсказки, как текстового значения, так и его валидации, мы получаем возможность использовать обращение к свойствам DIRECTIONS
объекта, то есть DIRECTIONS.UP
или DIRECTIONS.DOWN
.
Без объявления константного объекта и при использовании только объявления type, через конкатенацию |
значений, мы могли бы использовать только текстовое обозначение.
Так же этот вид объявления “перечисления” выдает чистый код, после транспиляции в JavaScript:
"use strict";
const DIRECTIONS = {
UP: 'UP',
DOWN: 'DOWN'
};
const someDirection1 = "UP";
const someDirection2 = "DOWN";
const someDirection3 = DIRECTIONS.UP;
Наличие const в коде на JS дает уверенность в том, что в JS не будет перезаписано значение объекта DIRECTIONS.
Чего не скажешь о транспиляции кода на TypeScript, с объявлением через enum:
enum DIRECTIONS {
UP,
DOWN,
};
в JavaScript:
"use strict";
var DIRECTIONS;
(function (DIRECTIONS) {
DIRECTIONS[DIRECTIONS["UP"] = 0] = "UP";
DIRECTIONS[DIRECTIONS["DOWN"] = 1] = "DOWN";
})(DIRECTIONS || (DIRECTIONS = {}));
;
- В глаза бросается объявление глобальной, для модуля переменной
DIRECTIONS
, что тоже не очень хорошо, так как значение переменной, может быть без труда переписано/переопределено и никто не узнает об этом. - Значения такого перечисления содержат не строку, а число, что вносит неразбериху, так как после выполнения вышеописанного кода, получается следующие значения для
DIRECTIONS
:
{0: "UP", 1: "DOWN", UP: 0, DOWN: 1}
которое содержит как мэппинг из числа в строку, так и мэппинг из строки в число (которым и будет пользоваться разработчик).
- Для получения текстового значения придется выполнить следующий кульбит в JS:
DIRECTIONS[DIRECTIONS.UP] // Вывод "UP"
В итоге с enum
мы можем сделать так:
const someDirection: DIRECTIONS.UP = 123;console.log(someDirection); // 123;
а с использованием правильного объявления через const и type, мы получим ошибку
'DIRECTIONS' only refers to a type, but is being used as a namespace here.(2702)Exported variable 'someDirection' has or is using private name 'DIRECTIONS'.(4025)
для кода
const someDirection: DIRECTIONS.UP = 123;
Заключение
При использовании TypeScript, нужно так же думать о результатах транспиляции в JS, не станет ли это проблемой для мира JS, который в итоге и будет исполняться в браузере или при помощи node на сервере.