En esta publicación tocaremos acciones de escritura y reductor. Para comenzar, considere un "flujo" típico, en el que realizamos las siguientes operaciones (además, reelaboramos todo para que nuestro código cumpla con los principios SÓLIDOS).
1. crear un archivo con constantes (aquí guardamos los nombres de los tipos de acción)
export const REQUEST_DATA_PENDING = "REQUEST_DATA_PENDING"; export const REQUEST_DATA_SUCCESS = "REQUEST_DATA_SUCCESS"; export const REQUEST_DATA_FAILED = "REQUEST_DATA_FAILED"; export const PROFILES_PER_PAGE = "PROFILES_PER_PAGE"; export const CURRENT_PAGE = "CURRENT_PAGE";
2. cree un archivo donde describamos las acciones (aquí hacemos una solicitud de cuentas de usuario y paginación). También en el ejemplo se utilizó redux-thunk (además rechazaremos dependencias similares):
export const requestBigDataAction = () => (dispatch) => { fetchingData(dispatch, BIG_DATA_URL, 50); } export const changeCurrentPAGE = (page) => ({ type: CURRENT_PAGE, payload: page }) function fetchingData(dispatch, url, profilesPerPage) { dispatch({type: REQUEST_DATA_PENDING}); fetch(url) .then((res) => { if(res.status !== 200) { throw new Error (res.status); } else { return res.json(); } }) .then((data) => {dispatch({type: REQUEST_DATA_SUCCESS, payload: data})}) .then(() => dispatch({type: PROFILES_PER_PAGE, payload: profilesPerPage})) .catch((err) => dispatch({type: REQUEST_DATA_FAILED, payload: ` . ${err.message}`})); }
3. escribimos reductor
import { REQUEST_DATA_PENDING, REQUEST_DATA_SUCCESS, REQUEST_DATA_FAILED, PROFILES_PER_PAGE, CURRENT_PAGE } from '../constants/constants'; const initialState = { isPending: false, buffer: [], data: [], error: "", page: 0, profilesPerPage: 0, detailedProfile: {} } export const MainReducer = (state = initialState, action = {}) => { switch(action.type) { case REQUEST_DATA_PENDING: return Object.assign({}, state, {isPending: true}); case REQUEST_DATA_SUCCESS: return Object.assign({}, state, {page : 0, isPending: false, data: action.payload, error: "", detailedProfile: {}, buffer: action.payload}); case REQUEST_DATA_FAILED: return Object.assign({}, initialState, {error: action.payload}); case PROFILES_PER_PAGE: return Object.assign({}, state, {profilesPerPage: action.payload}); case CURRENT_PAGE: return Object.assign({}, state, {page: action.payload}); default: return state; } }
4. configurar la tienda (usar middleware thunkMiddleware)
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import {createStore, applyMiddleware} from 'redux'; import {Provider} from 'react-redux'; import thunkMiddleware from 'redux-thunk'; import {MainReducer} from './reducers/mainReducer'; const store = createStore(MainReducer, applyMiddleware(thunkMiddleware)); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root'));
5. conecte el componente a redux
const mapDispatchToProps = (dispatch)=>{ return { onRequestBigData: (event) =>{ dispatch(requestBigDataAction()); } } };
conectar botones de paginación a redux
const mapDispatchToProps = (dispatch)=>{ return { onChangePage: (page) =>{ dispatch(changeCurrentPAGE(page)); } } };
Problema: nuestro reductor es una gran declaración de cambio, por lo tanto, al agregar una nueva acción o cambiar su comportamiento, necesitamos cambiar nuestro reductor, lo que viola los principios de SOlid (el principio de responsabilidad exclusiva y el principio de apertura / cercanía).
Solución: el polimorfismo nos ayudará. Agregue a cada acción el método de ejecución, que aplicará la actualización y devolverá el estado actualizado. Entonces nuestro reductor tomará la forma
export const MainReducer = (state = initialState, action) => { if(typeof action.execute === 'function') return action.execute(state); return state; };
ahora, al agregar una nueva acción, no necesitamos cambiar el reductor, y no se convierte en un gran monstruo.
Luego, abandona redux-thunk y reescribe las acciones
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import {createStore} from 'redux'; import {Provider} from 'react-redux';
vaya al componente conectado, cuya acción es asíncrona (deberá corregirse ligeramente)
const mapDispatchToProps = (dispatch)=>{ return { onRequestBigData: (event) =>{ requestBigDataAction(dispatch); }, } };
y pasar a las acciones mismas y agregarles el método de ejecución
const type = 'bla-bla'; const requestDataPending = {execute: state => ({...state, isPending: true}), type}; const requestDataSuccess = payload => ({ execute: function (state) { return ({...state, page : 0, isPending: false, data: payload, error: "", detailedProfile: {}, buffer: payload}) }, type}) const profilesPerPageAction = profilesPerPage => ({ execute: state => ({...state, profilesPerPage: profilesPerPage}), type }); const requestDataFailed = errMsg => state => ({...state, error: ` . ${errMsg}`}); function fetchingData(dispatch, url, profilesPerPage) { dispatch(requestDataPending); fetch(url) .then((res) => { if(res.status !== 200) { throw new Error (res.status); } else { return res.json(); } }) .then((data) => {dispatch(requestDataSuccess(data))}) .then(() => dispatch(profilesPerPageAction(profilesPerPage))) .catch((err) => dispatch(requestDataFailed(err.message))); } export const requestBigDataAction = (dispatch) => { fetchingData(dispatch, BIG_DATA_URL, 50); } export const changeCurrentPAGE = page => ({ type, execute: state => ({...state, page}) })
Nota: se requiere la propiedad type (si no la agrega, se generará una excepción). Pero para nosotros no importa en absoluto. Es por eso que ya no necesitamos un archivo separado que enumere los tipos de acciones.
PD: En este artículo, aplicamos los principios de SRP y OCP, polimorfismo, abandonamos la biblioteca de terceros e hicimos nuestro código más limpio y fácil de mantener.