Pull to refresh

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

В статье упоминание об этом имеется.
Sign up to leave a comment.

Articles