Скажите, люди, я один испытываю небольшой душевный зуд
от необходимости писать нечто вот эдакое? :
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
})
Да, конечно я понимаю, что это весьма мелочное нововведение, но мне от такого стиля работать с кодом на много приятней :)
И пожалуйста, не судите строго, если что — это мой первый пост на хабре
UPD:
Выхватив множество комментариев, интереснейших, но высказанных с разной долей недопонимания о том, что это вообще такое — redux-refine, я решил добавить ещё более детальное разъяснение:
Вот что я сделал:
1 Заменил конструкцию switch-case на выбор по ключу в хэшэ:
это
function reducer(state, {type, data}){
switch(type) {
case 'one': return {...state, ...data};
case 'two': return {...state, ...data};
case 'three': return {...state, ...data};
default: return state;
}
}
заменил на это
function reducer(state, {type, data}){
return ({
one: {...state, ...data},
two: {...state, ...data},
three: {...state, ...data}
})[type] || state;
}
2 Вынес хэш из скопа функции, что бы не пересоздавать его при каждом вызове, а каждое свойство этого хэша сделал методом чистой функцией, которой передаются необходимые данные в аргументах.
const reducers = {
one: (state, data) => ({...state, ...data}),
two: (state, data) => ({...state, ...data}),
three: (state, data) => ({...state, ...data})
}
function reducer(state, {type, data}){
return (redusers[type] || (state => state))(state, data);
}
3 Вместо объявления констант, использовал экспорт ключей хэша, так как они по определению уникальны, создав таким образом возможность импортировать эти ключи в экшенах из редьюсеров, что позволяет наглядно показать, с каким редьюсером работает данный модуль с экшенами.
И пожалуйста, не надо городить огород, так как redux-refine работает в точности так же, как и классический редьюсер — именно один в один, почему я и сказал, что можно его юзать одновременно с обычным подходом.
Это не какая то новая архитектура, не какой то там новый наворот — это просто улучшение кодестайла и обеспечение наглядности связей между модулями классического редакса.
К сожалению, не доступен сервер mySQL