Redux-symbiote: escribir acciones y reductores casi sin dolor

React-redux es una gran cosa. Cuando se usa correctamente, la arquitectura de la aplicación es eficiente y la estructura del proyecto es fácil de leer. Pero como en cualquier decisión, hay algunas peculiaridades.

La descripción de acciones y reductores es una de esas características. La implementación clásica de estas dos entidades en el código es una tarea que consume bastante tiempo.

El dolor de la implementación clásica.


Un simple ejemplo:

// actionTypes.js //    export const POPUP_OPEN_START = 'POPUP_OPEN_START '; export const POPUP_OPEN_PENDING = 'POPUP_OPEN_PENDING '; export const POPUP_OPEN_SUCCESS = 'POPUP_OPEN_SUCCESS '; export const POPUP_OPEN_FAIL = 'POPUP_OPEN_FAIL'; export const POPUP_CLOSE_START = 'POPUP_CLOSE_START '; export const POPUP_CLOSE_PENDING = 'POPUP_CLOSE_PENDING '; export const POPUP_CLOSE_SUCCESS = 'POPUP_CLOSE_SUCCESS '; export const POPUP_CLOSE_FAIL = 'POPUP_CLOSE_FAIL'; 

 // actions.js //    import { POPUP_OPEN_START, POPUP_OPEN_PENDING, POPUP_OPEN_SUCCESS, POPUP_OPEN_FAIL, POPUP_CLOSE_START, POPUP_CLOSE_PENDING, POPUP_CLOSE_SUCCESS, POPUP_CLOSE_FAIL } from './actionTypes'; export function popupOpenStart(name) { return { type: POPUP_OPEN_START, payload: { name }, } } export function popupOpenPending(name) { return { type: POPUP_OPEN_PENDING, payload: { name }, } } export function popupOpenFail(error) { return { type: POPUP_OPEN_FAIL, payload: { error, }, } } export function popupOpenSuccess(name, data) { return { type: POPUP_OPEN_SUCCESS, payload: { name, data }, } } export function popupCloseStart(name) { return { type: POPUP_CLOSE_START, payload: { name }, } } export function popupClosePending(name) { return { type: POPUP_CLOSE_PENDING, payload: { name }, } } export function popupCloseFail(error) { return { type: POPUP_CLOSE_FAIL, payload: { error, }, } } export function popupCloseSuccess(name) { return { type: POPUP_CLOSE_SUCCESS, payload: { name }, } } 

 // reducers.js //   import { POPUP_OPEN_START, POPUP_OPEN_PENDING, POPUP_OPEN_SUCCESS, POPUP_OPEN_FAIL, POPUP_CLOSE_START, POPUP_CLOSE_PENDING, POPUP_CLOSE_SUCCESS, POPUP_CLOSE_FAIL } from './actionTypes'; const initialState = { opened: [] }; export function popupReducer(state = initialState, action) { switch (action.type) { case POPUP_OPEN_START: case POPUP_OPEN_PENDING: case POPUP_CLOSE_START: case POPUP_CLOSE_PENDING: return { ...state, error: null, loading: true }; case POPUP_OPEN_SUCCESS : return { ...state, loading: false, opened: [ ...(state.opened || []).filter(x => x.name !== action.payload.name), { ...action.payload } ] }; case POPUP_OPEN_FAIL: return { ...state, loading: false, error: action.payload.error }; case POPUP_CLOSE_SUCCESS: return { ...state, loading: false, opened: [ ...state.opened.filter(x => x.name !== name) ] }; case POPUP_CLOSE_FAIL: return { ...state, loading: false, error: action.payload.error }; } return state; } 

La salida tiene 3 archivos y al menos los siguientes problemas:

  • "Hincha" el código simplemente agregando una nueva cadena de acciones
  • importación excesiva de constantes de acción
  • lectura de nombres constantes de acción (individualmente)

Optimización


Este ejemplo se puede mejorar con redux-actions .

 import { createActions, handleActions, combineActions } from 'redux-actions' export const actions = createActions({ popups: { open: { start: () => ({ loading: true }), pending: () => ({ loading: true }), fail: (error) => ({ loading: false, error }), success: (name, data) => ({ loading: false, name, data }), }, close: { start: () => ({ loading: true }), pending: () => ({ loading: true }), fail: (error) => ({ loading: false, error }), success: (name) => ({ loading: false, name }), }, }, }).popups const initialState = { opened: [] }; export const accountsReducer = handleActions({ [ combineActions( actions.open.start, actions.open.pending, actions.open.success, actions.open.fail, actions.close.start, actions.close.pending, actions.close.success, actions.close.fail ) ]: (state, { payload: { loading } }) => ({ ...state, loading }), [combineActions(actions.open.fail, actions.close.fail)]: (state, { payload: { error } }) => ({ ...state, error }), [actions.open.success]: (state, { payload: { name, data } }) => ({ ...state, error: null, opened: [ ...(state.opened || []).filter(x => x.name !== name), { name, data } ] }), [actions.close.success]: (state, { payload: { name } }) => ({ ...state, error: null, opened: [ ...state.opened.filter(x => x.name !== name) ] }) }, initialState) 

Ya mucho mejor, pero no hay límite para la perfección.

Tratar el dolor


En busca de una solución mejor, me encontré con un comentario de LestaD habr.com/en/post/350850/#comment_10706454 y decidí probar redux-symbiote .
Esto permitió eliminar entidades innecesarias y reducir la cantidad de código.

El ejemplo anterior comenzó a verse así:

 // symbiotes/popups.js import { createSymbiote } from 'redux-symbiote'; export const initState = { opened: [] }; export const { actions, reducer } = createSymbiote(initialState, { popups: { open: { start: state => ({ ...state, error: null }), pending: state => ({ ...state }), success: (state, { name, data } = {}) => ({ ...state, opened: [ ...(state.opened || []).filter(x => x.name !== name), { name, data }) ] }), fail: (state, { error } = {}) => ({ ...state, error }) }, close: { start: state => ({ ...state, error: null }), pending: state => ({ ...state }), success: (state, { name } = {}) => ({ ...state, opened: [ ...state.opened.filter(x => x.name !== name) ] }), fail: (state, { error } = {}) => ({ ...state, error }) } } }); 

 //   import { actions } from './symbiotes/popups'; // ... export default connect( mapStateToProps, dispatch => ({ onClick: () => { dispatch(actions.open.start({ name: PopupNames.Info })); } }) )(FooComponent); 

De los profesionales tenemos:

  • todo en un archivo
  • menos código
  • representación estructurada de acciones

De las desventajas:

  • IDE no siempre ofrece pistas
  • difícil de buscar acción en el código
  • acción difícil de cambiar el nombre

A pesar de las desventajas, este módulo se utiliza con éxito en nuestros proyectos.

Gracias a LestaD por el buen trabajo.

Source: https://habr.com/ru/post/442346/


All Articles