Wie man die Anzahl reduziert und die Lesbarkeit von Code in React-Redux, Redux-Saga erhöht

In diesem Artikel möchte ich meine Erfahrungen mit der Verwendung des React-Redux- und Redux-Saga-Bundles teilen , oder vielmehr mit welchem ​​„Fahrrad“ ich die Menge des gleichen Codetyps reduziere und dessen Wahrnehmung vereinfache.

Was passte nicht zu mir?


Die React-Redux- und Redux-Saga-Bibliotheken sind einfach, flexibel und bequem, weisen jedoch Code-Redundanz auf. Die Hauptelemente sind:

  1. Eventfabriken


    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);
    

    Als solches verwirren mich einige Dinge:

    - eine Beschreibung der Arten von Ereignissen. In diesem Beispiel können Sie zwar auf eine Konstante verzichten, müssen jedoch den Typ im Werk übergeben, der mit dem Namen der in der Kamelbenachrichtigung ( camelCase ) erstellten Ereignisse identisch ist .

    - Wenn Sie die Nutzlaststruktur vergessen haben, müssen Sie zum Abrufen zu Reducer / Saga gehen , wo dieses Ereignis verwendet wird, und sehen, was dort übertragen werden muss
  2. Reduzierstücke


    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;
    };
    

    Hier stört im Allgemeinen nur die Verwendung der Schalterkonstruktion
  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())]);
    }
    

    In Sagen ist das größte Problem die Schwierigkeit des Lesens. Nur ein flüchtiger Blick, um zu verstehen, was die Saga schwierig macht. Ein Grund dafür ist die tiefe Verschachtelung. Sie können natürlich die Arbeiter herausnehmen, um die Tiefe zu verringern, aber dann erhöht sich die Redundanz.

    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())]);
    }
    

    Es gibt auch Überprüfungen, die entweder die Verschachtelung erhöhen oder Ausstiegspunkte aus der Saga hinzufügen, was unseren Code verschlechtert.

Wie versuche ich diese Probleme zu lösen?


Lass uns in Ordnung gehen.

  1. Eventfabriken


    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(),
      ]);
    }
    

    Erstellen Sie eine Klasse mit der Annotation sagas (). Beim Erstellen einer Instanz der Klasse erhalten wir einen Funktionsgenerator, der alle mit den Anmerkungen takeEvery ([... [actionTypes]] oder takeLatest ([... [actionTypes]] ) gekennzeichneten Felder der Klasse in einem separaten Thread aufruft :

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

    Die Annotation filterActions ({state, type, payload}) kann auch für Felder verwendet werden. In diesem Fall wird die Saga nur aufgerufen, wenn die Funktion true zurückgibt .

Fazit


Mit diesen einfachen Anmerkungen wurden die Sagen im Allgemeinen klarer und sauberer, und die Menge an Code, die zum Hinzufügen neuer Logik benötigt wurde, wurde reduziert. Die Projektnavigation ist einfacher geworden.

Ich habe diese Anmerkungen im Sweet-Redux-Saga- Paket gemacht . Wenn es andere Lösungen gibt, teile ich diese gerne mit mir.

All Articles