[recovery mode] redux-refine — простая радость перфекциониста

image


Скажите, люди, я один испытываю небольшой душевный зуд
от необходимости писать нечто вот эдакое? :


export const ADD_TODO = 'ADD_TODO'
export const DELETE_TODO = 'DELETE_TODO'
export const EDIT_TODO = 'EDIT_TODO'
export const COMPLETE_TODO = 'COMPLETE_TODO'
export const COMPLETE_ALL = 'COMPLETE_ALL'
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED'


Я почему то думаю, что нет и иногда встречая в чьём то коде


if (action.type === ADD_TODO) {
  // ...
}


вместо ядрёного switch — case, я понимаю, что не единственный такой я на свете перфекционист, страдающий от этого «чуть-чуть не так как надо» в классическом Redux


Если Вам, уважаемый читатель, знакома эта боль, возрадуйтесь! под катом есть лекарство всего в две строчки кода :)


Особенно печально обстоят дела, когда Вы пытаетесь разобраться в крупном проекте со сложным UI. Вы видите, какие методы экшенов вызывает компонент, открываете файл с этими экшенами и хорошо, если там константы названы интуитивно, но бывают ситуации, когда Вы начинаете искать во всех редюсерах, куда же они прилетают.


По сути дела, dispatch — это метод Store, аналогичный по смыслу методу emit старого доброго EventEmitter и в терминах классической событийной модели, у нас фактически Store подписан на события, имена которых называются типами экшенов и которые принято задавать в виде вышеупомянутых констант, в связи с чем у меня постоянно возникал вопрос, почему я должен хранить это где то отдельно, да к тому же повторно прибегая к такому нелепому дублированию кода? Исходная мысль то ясна, нам необходимо подстраховаться от конфликтов и обеспечить некоторую консистентность между экшенами и редюсерами, но не уже ли нельзя сделать это как то элегантней?


Я понимаю, что люди разные и если у кого то возникнет аргументированное возражение на этот мой лёгкий дискомфорт от работы с кодом Redux, буду рад выслушать любые мнения в комментариях, но тем, кто разделяет сие чувство, позвольте представить redux-refine


Идея в основе проста:


Я предлагаю использовать вместо switch-case хэш, индексированный типом экшенов, так как в объекте не может быть одинаковых свойств, что исключает конфликты в рамках одного редьюсера, а так же позволяет экспортировать типы экшенов для модуля, из которого они диспатчатся


Так же, такой подход обеспечивает чистую связность кода, следующую логике one way binding и отражающему направление потока данных в приложении, а именно:
мы видим в компоненте, методы какого модуля с экшенами он использует, а в модуле с экшенами мы видим, каким редюсерам он отправляет экшены.


По просьбе tmnhy на наглядном примере поясню:


в экшенах мы делаем так:


import { actionTypes as types1 } from 'reducers/reducer1'
import { actionTypes as types2 } from 'reducers/reducer2'

const { ACTION_1_1, ACTION_1_2, ACTION_1_3 } = types1
const { ACTION_2_1, ACTION_2_2, ACTION_2_3 } = types2


в редюсерах так:


reducer1:


import { getActionTypes, connectReducers } from 'redux-refine'

export const initialState = {
  value1: 0,
  value2: '',
  value3: null,
}

const reducers = {
  ACTION_1_1: (state, {value1}) => ({...state, value1}), 
  ACTION_1_2: (state, {value2}) => ({...state, value2}), 
  ACTION_1_3: (state, {value3}) => ({...state, value3}), 
}

export const actionTypes = getActionTypes(reducers)
export default connectReducers(initialState, reducers)


reducer2:


import { getActionTypes, connectReducers } from 'redux-refine'

export const initialState = {
  value1: 0,
  value2: '',
  value3: null,
}

const reducers = {
  ACTION_2_1: (state, {value1}) => ({...state, value1}), 
  ACTION_2_2: (state, {value2}) => ({...state, value2}), 
  ACTION_2_3: (state, {value3}) => ({...state, value3}), 
}

export const actionTypes = getActionTypes(reducers)
export default connectReducers(initialState, reducers)


в том месте, где Вы предпочитаете комбинировать редюсеры всё по прежнему:


import { combineReducers } from 'redux'

import reducer1, { initialState as stateSection1 } from './reducer1'
import reducer2, { initialState as stateSection2 } from './reducer2'

export const intitialState = {
  stateSection1, stateSection2
}

export default combineReducers({
  stateSection1: reducer1,
  stateSection2: reducer2
})


Да, конечно я понимаю, что это весьма мелочное нововведение, но мне от такого стиля работать с кодом на много приятней :)


И пожалуйста, не судите строго, если что — это мой первый пост на хабре

© Habrahabr.ru