Dans cet article, nous aborderons les actions d'écriture et de réduction. Pour commencer, considérons un «flux» typique, dans lequel nous effectuons les opérations suivantes (en outre, nous retravaillons tout pour que notre code réponde aux principes SOLIDES).
1. créer un fichier avec des constantes (ici on enregistre les noms des types d'actions)
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. créez un fichier où nous décrivons les actions (ici nous faisons une demande de comptes utilisateurs et de pagination). Toujours dans l'exemple, redux-thunk a été utilisé (nous refuserons en outre des dépendances similaires):
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. nous écrivons réducteur
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. configurer le magasin (utiliser le 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. connectez le composant à redux
const mapDispatchToProps = (dispatch)=>{ return { onRequestBigData: (event) =>{ dispatch(requestBigDataAction()); } } };
connecter les boutons de pagination au redux
const mapDispatchToProps = (dispatch)=>{ return { onChangePage: (page) =>{ dispatch(changeCurrentPAGE(page)); } } };
Problème: notre réducteur est une grande déclaration d'interrupteur, par conséquent, lors de l'ajout d'une nouvelle action ou de la modification de son comportement, nous devons changer notre réducteur, ce qui viole les principes de SOlid (le principe de la responsabilité exclusive et le principe d'ouverture / de proximité).
Solution: le polymorphisme nous aidera. Ajoutez à chaque action la méthode d'exécution, qui appliquera la mise à jour et retournera l'état mis à jour. Ensuite, notre réducteur prendra la forme
export const MainReducer = (state = initialState, action) => { if(typeof action.execute === 'function') return action.execute(state); return state; };
maintenant, lorsque vous ajoutez une nouvelle action, nous n'avons pas besoin de changer de réducteur, et cela ne se transforme pas en un énorme monstre.
Ensuite, abandonnez le redux-thunk et réécrivez les actions
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';
aller au composant connecté, dont l'action est asynchrone (il faudra la corriger légèrement)
const mapDispatchToProps = (dispatch)=>{ return { onRequestBigData: (event) =>{ requestBigDataAction(dispatch); }, } };
et passer aux actions elles-mêmes et leur ajouter la méthode d'exécution
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}) })
Remarque: la propriété type est obligatoire (si vous ne l'ajoutez pas, une exception sera levée). Mais pour nous, cela n'a pas d'importance du tout. C'est pourquoi nous n'avons plus besoin d'un fichier séparé répertoriant les types d'actions.
PS: Dans cet article, nous avons appliqué les principes de SRP et OCP, le polymorphisme, abandonné la bibliothèque tierce et rendu notre code plus propre et maintenable.