Одновременно работая над JavaScript и Typescript, мне не хватает некоторых основных функций Typescript
в обычном JavaScript. Одна из интересных особенностей Typescript - Enum.
Enum помогает создать объект состояния (Enums: константа с фиксированными значениями).
Подумайте о том, что вы работаете над приложением для загрузки файлов. Для вашего приложения-загрузчика необходимо как минимум три состояния.
- НАЧАЛО
- INPROGRESS
- ЗАВЕРШЕНО
Да, вы можете создать Map<number, any>::map[object]
`. Карта будет выглядеть так:
const STATES = { START: 0, INPROGRESS: 1, FINISHED: 3, };
Хорошее начало! Но по какой-то причине мы хотим запустить объект STATES с “1”
вместо “0”
. Давай изменим это.
const STATES = { START: 1, INPROGRESS: 2, FINISHED: 3, };
Выполнено! Это выглядит мило. Однако нам пришлось изменить 3 значения. Здесь, в этом коде, все еще управляемо.
Здесь возникает новое требование. Теперь у нас есть значение состояния, и нам нужно найти текущий state name
.
Код:
const STATES = { START: 1, INPROGRESS: 2, FINISHED: 3, }; const getState = (val) => Object.entries(STATES).find(([_, value]) => value === val)[0]; const currentState = 2; // equivalent of INPROGRESS console.log(getState(currentState)); // INPROGRESS // Output: INPROGRESS
Работает нормально! Не правда ли! Но подождите, мы забываем протестировать API. Давай проверим.
Примечание: я использую узел js assert module.
const assert = require("assert"); const STATES = { START: 1, INPROGRESS: 2, FINISHED: 3, }; const getState = (val) => Object.entries(STATES).find(([_, value]) => value === val)[0]; assert.equal(getState(2), "INPROGRESS") || console.log(`getState(2) === "INPROGRESS" // PASS`); // try again with negative test assert.equal(getState(4), undefined) || console.log(`getState(2) === undefined // PASS`);
Вывод:
getState(2) === "INPROGRESS" // PASS /Users/xdeepakv/LearnAndShare/learn-js/plain-html/tempCodeRunnerFile.js:8 Object.entries(STATES).find(([_, value]) => value === val)[0]; ^ TypeError: Cannot read property '0' of undefined
Первое утверждение работает нормально. Однако второе утверждение неверно. Причина, по которой мы забыли добавить null/undefined
проверяемую находку. давай исправим это.
//.. rest of the code const getState = (val) => { const obj = Object.entries(STATES).find(([_, value]) => value === val) || []; return obj[0]; }; assert.equal(getState(2), "INPROGRESS") || console.log(`getState(2) === "INPROGRESS" // PASS`); // Output: getState(2) === "INPROGRESS" // PASS // try again with negative test assert.equal(getState(4), undefined) || console.log(`getState(2) === undefined // PASS`); //Output: getState(2) === undefined // PASS
Все тестовые случаи проходят. Однако нам пришлось написать много кода, чтобы простая вещь работала.
Для оптимизации вы можете сказать, что мы можем создать map/object
ключей и значений при создании впервые. Ага! Давай сделаем это.
//.. rest of the code.. const reveseState = Object.keys(STATES).reduce((m, key) => { m[STATES[key]] = key; return m; }, {}); const getState = (val) => reveseState[val]; assert.equal(getState(2), "INPROGRESS") || console.log(`getState(2) === "INPROGRESS" // PASS`); // try again with negative test assert.equal(getState(4), undefined) || console.log(`getState(2) === undefined // PASS`);
Все снова хорошо. Но по-прежнему нужно управлять большим количеством кода. Нам пришлось создать карту reverseState
, перебирая все значения. Но мы можем убрать это больше.
Перенесем это в класс.
const assert = require("assert"); class DownloadState { constructor() { const STATES = { START: 1, INPROGRESS: 2, FINISHED: 3, }; this._reveseState = Object.keys(STATES).reduce((m, key) => { m[STATES[key]] = key; return m; }, {}); this.STATES = STATES; } getState(val) { return this._reveseState[val]; } values() { return this.STATES; } }
Как использовать
const downlaodState = new DownloadState(); const { START } = downlaodState.STATES; assert.equal(START, 1) || console.log(`START === 1 // PASS`); // Output: START === 1 // PASS assert.equal(downlaodState.getState(2), "INPROGRESS") || console.log(`getState(2) === "INPROGRESS" // PASS`); // Output: getState(2) === "INPROGRESS" // PASS
Теперь у нас есть класс DownloadState
, и все работает как положено. Однако слишком много шаблонного кода, чтобы справиться только за one Enum
. Если нам нужен другой Enum, похожий, но с другим значением. Нам нужно создать еще одну карту / класс / объект.
Давайте создадим utility
функцию.
const enumVars = (args, startIndex = 1) => { return args.split("|").reduce((m, key, index) => { m[(m[key] = index + startIndex)] = key; return m; }, {}); }; const DOWNLOAD_STATES = enumVars("START|INPROGRESS|FINISHED", 1); console.log(DOWNLOAD_STATES); // { '1': 'START', '2': 'INPROGRESS', '3': 'FINISHED', START: 1, INPROGRESS: 2, FINISHED: 3 } const { START } = DOWNLOAD_STATES; console.log(`CURR STATE is ${START}`) // CURR STATE is 1 console.log(`CURR STATE is ${DOWNLOAD_STATES[2]}`) // CURR STATE is INPROGRESS
Ага! Намного чище, чем все предыдущие реализации. Это даже может справиться с startIndex
и reverseMapping
тоже.
Но что, если я скажу, что мы можем очистить его еще с помощью Прокси. Давайте создадим Enum
class.
class Enum { constructor(args, startIndex = 0, defaultValue) { const em = args.split("|").reduce((m, key, index) => { m[(m[key] = index + startIndex)] = key; return m; }, {}); em.value = em.key = (k) => em[k]; return new Proxy(em, { get: (obj, prop) => (prop in obj ? obj[prop] : defaultValue), set: (obj, prop, value) => { if (prop in obj) { throw new Error("cant set same value again"); // // cant set same value again } em[(em[prop] = value)] = prop; }, }); } }
Как использовать:
const DOWNLOAD_STATES = new Enum("START|INPROGRESS|FINISHED", 1); console.log(JSON.stringify(DOWNLOAD_STATES)); //{"1":"START","2":"INPROGRESS","3":"FINISHED","START":1,"INPROGRESS":2,"FINISHED":3} const { START } = DOWNLOAD_STATES; console.log(`CURR STATE is ${START}`); //Output: CURR STATE is 1 console.log(`CURR STATE is ${DOWNLOAD_STATES[2]}`); //Output: CURR STATE is INPROGRESS // ADD new State: DOWNLOAD_STATES.IDLE = 4; console.log(`CURR STATE is ${DOWNLOAD_STATES[4]}`); //Output: CURR STATE is IDLE
ТАДА !! Все хорошо. Реализация выглядит более безопасной с awesome Proxy
. Здесь мы также можем добавить новое состояние IDLE
.
Вы можете найти последний рабочий код в моей сущности.
Бонус: машинописная версия
class Enum<Proxy> { readonly em: Map<string, number>; readonly defaultValue: any; constructor(args: string, startIndex = 0, defaultValue = undefined) { const em = args.split("|").reduce((m, key, index) => { m[(m[key] = index + startIndex)] = key; return m; }, {} as any); em.value = em.key = (k: string) => em[k]; this.em = em; this.defaultValue = defaultValue; } build(): any { return new Proxy<Map<string, number>>(this.em, { get: (obj, prop) => (prop in obj ? obj[prop] : this.defaultValue), set: (obj, prop, value) => { if (prop in obj) { throw new Error("cant set same value again"); // // cant set same value again } this.em[(this.em[prop] = value)] = prop; return true; }, }); } } // create insatnce using build const DownloadState = new Enum<any>("START|DONE", 10).build(); console.log(DownloadState.START);
Надеюсь, это руководство помогло вам лучше понять Enum! Спасибо. 🙏
Продолжайте кодировать !! Ваше здоровье! 🍻🍻