Cara mengurangi angka dan meningkatkan pembacaan kode di react-redux, redux-saga

Dalam artikel ini saya ingin berbagi pengalaman saya menggunakan bundel react-redux dan redux-saga , atau lebih tepatnya, "sepeda" yang saya gunakan untuk mengurangi jumlah jenis kode yang sama dan menyederhanakan persepsinya.

Apa yang tidak cocok untukku


The bereaksi-redux dan redux-saga perpustakaan sederhana, fleksibel, dan nyaman, tapi memiliki kode redundansi. Unsur utama adalah:

  1. Pabrik acara


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

    Karena itu, beberapa hal membingungkan saya:

    - deskripsi jenis acara. Dalam contoh ini, Anda tentu dapat melakukannya tanpa konstan, tetapi masih harus melewati tipenya di pabrik, yang akan identik dengan nama acara yang dibuat dalam pemberitahuan unta ( camelCase ).

    - jika Anda lupa struktur payload , untuk mengingatnya, Anda harus pergi ke reducer / saga , di mana acara ini digunakan, dan lihat apa yang perlu ditransfer ke sana
  2. Reduksi


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

    Di sini, secara umum, hanya penggunaan konstruksi sakelar yang mengganggu
  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())]);
    }
    

    Dalam kisah-kisah, masalah terbesar adalah kesulitan membaca. Pandangan sekilas untuk memahami apa yang membuat saga itu sulit. Salah satu alasannya adalah sarang yang dalam. Anda tentu saja dapat mengambil pekerja untuk mengurangi kedalaman, tetapi kemudian redundansi meningkat.

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

    Ada juga pemeriksaan yang akan meningkatkan bersarang, atau menambahkan titik keluar dari saga, yang memperburuk kode kami.

Bagaimana saya mencoba menyelesaikan masalah ini


Mari kita mulai.

  1. Pabrik acara


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

    buat kelas dengan penjelasan sagas (). Saat membuat turunan kelas, kita mendapatkan generator fungsi yang memanggil semua bidang kelas yang ditandai dengan takeEvery ([... [actionTypes]])) atau takeLatest ([... [actionTypes]] anotasi di utas terpisah:

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

    anotasi filterActions ({state, type, payload}) juga dapat digunakan dengan bidang , dalam hal ini saga akan dipanggil hanya jika fungsi mengembalikan true .

Kesimpulan


Secara umum, menggunakan penjelasan sederhana ini, kisah-kisah menjadi lebih jelas dan lebih bersih, dan jumlah kode yang diperlukan untuk menambahkan logika baru berkurang. Navigasi proyek menjadi lebih mudah.

Saya membuat anotasi ini dalam paket sweet-redux-saga . Jika ada solusi lain, saya akan senang berbagi dengan saya.

All Articles