[recovery mode] redux-refine — простая радость перфекциониста
Скажите, люди, я один испытываю небольшой душевный зуд
от необходимости писать нечто вот эдакое? :
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
})
Да, конечно я понимаю, что это весьма мелочное нововведение, но мне от такого стиля работать с кодом на много приятней :)
И пожалуйста, не судите строго, если что — это мой первый пост на хабре