Комментарии 20
- для него есть поддержка в switch/case выражениях
- его удобно использовать в протокольных объектах
Вопрос в том, что стандартный enum нельзя расширить до требуемой функциональности. Нам, например, нужно все 'наши' перечисления отображать в таблице тем цветом, который указан в конструкторе:
@Enum("text")
export class State extends EnumType<State>() {
static readonly NEW = new State("Новый", Color.BLACK);
static readonly ACTIVE = new State("Действующий", Color.GREEN);
static readonly BLOCKED = new State("Blocked", Color.RED);
private constructor(public text: string, public color:string) {
super();
}
}
Color — не единственный атрибут, который требуется нашей бизнес логике. Как правило это пара: code и text.
Во-вторых, с теоретической точки зрения это тоже приветствуется. Таким образом без лишних телодвижений реализуются паттерны state и strategy, что позволяет заменить условную логику полиморфизмом. Вместо switch и if просто делегируем часть функциональности енаму. Про то, что полиморфизм > условная логика, в последнее десятилетие не писал только ленивый.
Это отличная фича в Java. И мне этого очень не хватает в Typescript.
Более хороший пример: TimeUnit из java.
В этом примере можно увидеть классический enum + некоторые связанные с ним методы.
К примеру, это можно использовать для конфигов:
{ "duration": 3, "timeUnit": "HOURS" }
const timeUnit = TimeUnit.valueOf(config.timeUnit || "MILLISECONDS");
const durationMs = timeUnit.toMillis(config.duration);
В случае с классическим enum, нужно было бы описывать сам enum и плодить пачку независимых функций для конвертации, и тут уже все будет зависеть от парадигмы программирования: OOP vs FP.
TypeScript тем и хорош что придает коду строгость, а вы это теряете повсеместно используя сырые типы (как any, или просто string)
1. Про какую повсеместность Вы говорите? Если об этапе использования указанного Enum, то как раз все и затевалось, чтобы конкретный класс енума, в примере, State, обладал всеми свойствами базового класса, но строгой типизацией под свои нужды. Проверить легко, в IDE набираем State+точка и смотрим что она предлагает.
2. Если речь идет о самой реализации декоратора/базового класса — Ваша правда, но дело поправимое: ждем commit от меня на гитхабе.
3. Почему string это сырой тип?
Вместо type-safety на этапе разработки по-прежнему работаете «в стиле JS» делая рантайм проверки (throw new Error).
В моем случае, проверки (throw new Error) и «стиль JS» никак не связаны. На это есть свои причины:
// Пример 1: лишний декоратор - ошибка при мерже веток после рефакторинга объекта State
@Enum("text")
@Enum("code")
class State extends EnumType<State> {
// ...
}
// Пример 2: забыли декоратор - неудачная копипаста
class State extends EnumType<State> {
// ...
}
// Пример 3 (js-code): создание экземпляра объекта вне области класса - защита от дурака
// функция конструктор класса State
var StateType = require("moduleName/state").State;
var deleteState = new StateType("Удален", Color.BLUE);
Я бы так сказал: все идет от декоратора, который пока не умеет менять тип исходного объекта.
Надеюсь дело поправимое Class Decorator Mutation
… а здесь может сложится ошибочное мнение о высоком уровне type-safety
Да. Я и команда знаем это. Но дух самой идеи декоратора таков, что легко можно накосячить. И не важно как и где вы их применяете. Примеры 1 и 2 тому подтверждение, или вот Enum(«incorrect_field_name»)
Если не получается задизайнить решение на TS с высоким уровнем type-safety, то может быть вообще не стоит это делать на TS
Здесь не вижу проблемы. Даже если не удастся мне прийти в решении к type-safety, есть тесты в конце концов. В целом предложение странное. Многие вещи сейчас не типизированы так как хочется. Например, связка vue-class-component и миксины, элементы самого vue (кто в теме $refs.myComponent) и многое другое. Я не в коем случае не говорю, что это хорошо, но однозначно не является причиной, чтобы отказываться от такого мощного языка как TypeScript
Группе разработчиков дали задание разработать самый технологичный и современный стек, не особо ограничивая по срокам. Ну как тут обойтись без оверинжиниринга и своих велосипедов? ;)
А если серьезно, то для вашей задачи присвоения дополнительных полей атрибутам есть два решения:
1) Пользоваться обычными enum, а недостающие значения доставать через маппинг:
enum State {
NEW, ACTIVE, BLOCKED
}
const statesMap: Record<State, IStateData> = {
[State.NEW]: { color: Color.BLACK, title: "New", value: State.NEW },
// ...
};
function getState(state: State): IStateData {
return statesMap[state];
}
console.log(getState(State.NEW).title); // "New"
Плюс в том, что у разных модулей могут быть свои маппинги по какой-то собственной логикой. Про смежение бизнес-логики и отображения вам уже выше написали.
2) Если уж очень хочется именно enum-классы, то и они уже написаны до вас: https://github.com/LMFinney/ts-enums Там более идеоматичное для typescript решение, без использования any, что плохо для строгой типизации и декораторов, которые еще экспериментальные, включаются флагом и их лучше не использовать в проектах на много лет и миллионов строк.
Пользоваться обычными enum, а недостающие значения доставать через маппинг:
Многие выше предлагают такой подход, но мало кто сравнивает и анализирует плюсы и минусы.
Конечно, это тема для отдельной статьи, но если кратко пройтись по неудобству работы с мапой:
— не все можно сделать с помощью мапы, см TimeUnit, (коммент выше от AndreyRubankov)
— мапа, это все же не енум. Иногда требуется прикладная/функциональная завершенность: смотришь на объект и сразу все понимаешь.
— подход с мапой более многословен, из твоего же примера в проде обращение к title будет выглядеть так: StateScope.getState(State.NEW).title vs State.NEW.title
— работа в браузере и дебаг: встречаешь ты значение 77 при отладке, а шо це таке?
И это только навскидку.
У каждого инструмента есть свои преимущества и недостатки. Я в первую очередь попытался создать инструмент. А подходит он или нет решать Вам по конкретным обстоятельствам.
Если уж очень хочется именно enum-классы, то и они уже написаны до вас...
В статье упоминание об этом имеется.
Война с TypeScript или покорение Enum