Cómo reducir el número y aumentar la legibilidad del código en react-redux, redux-saga

En este artículo me gustaría compartir mi experiencia de usar el paquete react-redux y redux-saga , o mejor dicho, qué "bicicleta" utilizo para reducir la cantidad del mismo tipo de código y simplificar su percepción.

Lo que no me convenía


Las bibliotecas react-redux y redux-saga son simples, flexibles y convenientes, pero tienen redundancia de código. Los elementos principales son:

  1. Fábricas de eventos


    const actionCreatorFactory = type => payload => ({ type, payload });
    
    export const INITIALIZE = 'INITIALIZE';
    
    export const ON_FIELD_CHANGE = 'ON_FIELD_CHANGE';
    export const onFieldChange = actionCreatorFactory(ON_FIELD_CHANGE);
    
    export const HANDLE_FIELD = 'HANDLE_FIELD';
    export const handleField = actionCreatorFactory(HANDLE_FIELD);
    
    export const GO_TO_NEXT_STEP = 'GO_TO_NEXT_STEP';
    export const goToNextStep = actionCreatorFactory(GO_TO_NEXT_STEP);
    

    Como tal, varias cosas me confunden:

    - una descripción de los tipos de eventos. En este ejemplo, ciertamente puede prescindir de la constante, pero aún debe pasar su tipo en la fábrica, que será idéntico al nombre de los eventos creados en la notificación de camello ( camelCase ).

    - si olvidó la estructura de la carga útil , para recuperarla, debe ir a reductor / saga , donde se usa este evento, y ver qué debe transferirse allí
  2. Reductores


    import getInitialState, {
      formDataInitialState as initialState,
    } from '../helpers/initialState';
    import { HANDLE_FIELD_DONE, ON_FIELD_CHANGE, RESET } from '../actionCreators';
    
    export default (state = initialState, { type, payload }) => {
      switch (type) {
        case RESET: {
          return getInitialState().formDataInitialState;
        }
    
        case ON_FIELD_CHANGE: {
          const { name } = payload;
          return {
            ...state,
            [name]: '',
          }
        }
    
        case HANDLE_FIELD_DONE: {
          const { name, value } = payload;
          return {
            ...state,
            [name]: value,
          }
        }
      }
    
      return state;
    };
    

    Aquí, en general, solo la construcción del interruptor es molesta
  3. Sagas


    import { all, put, select, fork, takeEvery } from 'redux-saga/effects';
    import { runServerSideValidation } from '../actionCreators';
    import { HANDLE_FIELD } from '../actionCreators';
    
    function* takeHandleFieldAction() {
      yield takeEvery(HANDLE_FIELD, function*({ payload }) {
        const { validation, formData } = yield select(
          ({ validation, formData }) => ({
            validation: validation[payload.name],
            formData,
          })
        );
    
        const valueFromState = formData[payload.name];
    
        if (payload.value !== valueFromState) {
          const { name, value } = payload;
          const { validator } = validation.serverValidator;
          yield put(
            runServerSideValidation({
              name,
              value,
              validator,
              formData,
            })
          );
        }
      });
    }
    
    export default function* rootSaga() {
      yield all([fork(takeHandleFieldAction())]);
    }
    

    En las sagas, el mayor problema es la dificultad de leer. Solo una mirada superficial para comprender lo que dificulta la saga. Una razón para esto es la anidación profunda. Por supuesto, puede eliminar a los trabajadores para reducir la profundidad, pero luego aumenta la redundancia.

    import { all, put, select, fork, takeEvery } from 'redux-saga/effects';
    import { runServerSideValidation } from '../actionCreators';
    import { HANDLE_FIELD } from '../actionCreators';
    
    function* takeHandleFieldWorker({ payload }) {
        const { validation, formData } = yield select(
          ({ validation, formData }) => ({
            validation: validation[payload.name],
            formData,
          })
        );
    
        const valueFromState = formData[payload.name];
    
        if (payload.value !== valueFromState) {
          const { name, value } = payload;
          const { validator } = validation.serverValidator;
          yield put(
            runServerSideValidation({
              name,
              value,
              validator,
              formData,
            })
          );
      }
    }
    
    function* takeHandleFieldWatcher() {
      yield takeEvery(HANDLE_FIELD, takeHandleFieldWorker);
    }
    
    export default function* rootSaga() {
      yield all([fork(takeHandleFieldWatcher())]);
    }
    

    También hay controles que aumentarán el anidamiento o agregarán puntos de salida de la saga, lo que empeora nuestro código.

¿Cómo estoy tratando de resolver estos problemas?


Vamos en orden.

  1. Fábricas de eventos


    import { actionsCreator, payload } from 'sweet-redux-saga';
    
    @actionsCreator()
    class ActionsFactory {
      initialize;
      
      @payload('field', 'value')
      onFieldChange;
      
      @payload('field')
      handleField;
      
      @payload('nextStep')
      goToNextStep;
    }
    

    actionsCreator(). , (initialize;/onFieldChange;/handleField;/gpToNextStep;) action creator. , payload(...[fieldNames]). :

    class ActionsFactory {
      initialize = () => ({
        type: 'INITIALIZE',
        payload: undefined,
      });
    
      onFieldChange = (field, value) => ({
        type: 'ON_FIELD_CHANGE',
        payload: {
          field,
          value,
        },
      });
    
      handleField = field => ({
        type: 'HANDLE_FIELD',
        payload: {
          field,
        },
      });
    
      goToNextStep = nextStep => ({
        type: 'GO_TO_NEXT_STEP',
        payload: {
          nextStep,
        },
      });
    }
    

    toString, toPrimitive, valueOf. :

    const actionsFactory = new ActionsFactory();
    console.log(String(actionsFactory.onFieldChange)); // 'ON_FIELD_CHANGE'
    

  2. import getInitialState, {
      formDataInitialState as initialState,
    } from '../helpers/initialState';
    import { HANDLE_FIELD_DONE, ON_FIELD_CHANGE, RESET } from '../actionCreators';
    import { reducer } from '../../../leadforms-gen-v2/src/decorators/ReducerDecorator';
    
    @reducer(initialState)
    export class FormDataReducer {
      [RESET]() {
        return getInitialState().formDataInitialState;
      }
    
      [ON_FIELD_CHANGE](state, payload) {
        const { name } = payload;
        return {
          ...state,
          [name]: '',
        };
      }
    
      [HANDLE_FIELD_DONE](state, payload) {
        const { name, value } = payload;
        return {
          ...state,
          [name]: value,
        };
      }
    }
    

    reducer([initialState]). , , .

    function reducer(state = initialState, action) {
      if (!action) {
        return state;
      }
    
      const reducer = instance[action.type];
      if (reducer && typeof reducer === 'function') {
        return reducer(state, action.payload);
      }
    
      return state;
    }
    

  3. import { all, put, select, } from 'redux-saga/effects';
    import { runServerSideValidation } from '../actionCreators';
    import { HANDLE_FIELD } from '../actionCreators';
    import { sagas, takeEvery, filterActions } from 'sweet-redux-saga';
    
    @sagas()
    class MySagas {
      @takeEvery([HANDLE_FIELD])
      @filterActions(
        ({state, payload }) => state.formData[payload.name] === payload.value
      )
      * takeHandleFieldAction({ payload }) {
        const { validation, formData } = yield select(({ validation, formData }) => ({
          validation: validation[payload.name],
          formData,
        }));
    
        const { name, value } = payload;
        const { validator } = validation.serverValidator;
        yield put(
          runServerSideValidation({
            name,
            value,
            validator,
            formData,
          })
        );
      }
    }
    
    export default function* rootSaga() {
      yield all([
        new MySagas(),
      ]);
    }
    

    crear una clase con anotación sagas (). Al crear una instancia de la clase, obtenemos un generador de funciones que llama a todos los campos de la clase marcados con anotaciones takeEvery ([... [actionTypes]]) o takeLatest ([... [actionTypes]] en un hilo separado:

    function* mySagas() {
      yield all([fork(mySagas.takeHandleFieldAction())]);
    }
    

    La anotación filterActions ({estado, tipo, carga útil}) también se puede usar con campos , en este caso la saga se llamará solo si la función devuelve verdadero .

Conclusión


En general, al usar estas simples anotaciones, las sagas se volvieron más claras y limpias, y se redujo la cantidad de código necesario para agregar una nueva lógica. La navegación del proyecto se ha vuelto más fácil.

Hice estas anotaciones en el paquete sweet-redux-saga . Si hay otras soluciones, estaré encantado de compartir conmigo.

All Articles