Как стать автором
Обновить

Комментарии 20

Вопрос не в том, что не подошел enum от TypeScript, мы также им пользуемся и он весьма полезен:
  • для него есть поддержка в 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.
НЛО прилетело и опубликовало эту надпись здесь
Есть такое, отрицать не буду. Но как показала практика, ущерб от этого смешивания — невелик или вообще отсутствует, а польза есть. Всего один класс EnumRenderer для отображения всех объектов такого типа.
НЛО прилетело и опубликовало эту надпись здесь
И такое тоже есть. Пока правда в одном единственном месте. На этот случай написан, свой Renderer.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Они не разрушают пользу от енамов. Поля (разумеется, константные) в енамах — это удобно. Это широко используется, например, в Java. Позволяет избавиться от лишних условных конструкций в коде.
НЛО прилетело и опубликовало эту надпись здесь
Это вы зря. Во-первых, это реально удобно на практике. Если каждому элементу енама соответствует какое-то значение или даже поведение (в Java для enum элементов можно определить метод), то удобно этот аттрибут или поведение сохранить в самом енаме.

Во-вторых, с теоретической точки зрения это тоже приветствуется. Таким образом без лишних телодвижений реализуются паттерны state и strategy, что позволяет заменить условную логику полиморфизмом. Вместо switch и if просто делегируем часть функциональности енаму. Про то, что полиморфизм > условная логика, в последнее десятилетие не писал только ленивый.

Это отличная фича в Java. И мне этого очень не хватает в Typescript.
В rust к примеру enum'ы тоже могут хранить дополнительные поля, причем один элемент может его иметь а другой может не иметь. Очень удобно на практике.
в typescript можно делать enum на стрингах. Я к тому что в typescript оно работает немного не так (это набор констант без значений)
Поправьте если я ошибся, но мне кажется вы просто заменили HashMap в пару ключ значение и назвали это расширенных enum. Я просто не понимаю почему тогда нельзя объявить HashMap, где ключем будет один из enum значений, а значением набор code, text, color. Если же я неправильно понял, то можно описать требуемую enumа функциональность
Я без примеров с Вашей стороны, не смогу показать разницы.
Думаю, reforms просто привел не самый лучший пример. Да, действительно хранить свойства отображения в enum – не лучшее решение, придет требование вводить Theme и будет много боли. Все же emun — это про логику/состояние, а не про отображение/визуализацию.

Более хороший пример: 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). Вместо type-safety на этапе разработки по-прежнему работаете «в стиле JS» делая рантайм проверки (throw new Error). Я бы сказал что подобное использовать в продакешене вреднее чистого JS, тк в случае JS хотябы ясно что это чистая динамика, а здесь может сложится ошибочное мнение о высоком уровне type-safety. Если не получается задизайнить решение на TS с высоким уровнем type-safety, то может быть вообще не стоит это делать на TS.
Давайте разбираться вместе, по пунктам. Моя правда такова:
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-классы, то и они уже написаны до вас...

В статье упоминание об этом имеется.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации