de Gabriele Cimato

Cum să creați cu ușurință primul dvs. middleware Redux

Cum sa creati cu usurinta primul dvs middleware
Fotografie de JESHOOTS.COM pe Unsplash

Aproape fiecare aplicație React cu cuvânt real folosește pe larg cererile asincronizate. Dacă gestionați starea aplicației cu Redux, există mai multe moduri de a gestiona acțiunile asincronizate.

Poate ai auzit de redux-thunksau redux-saga, cele mai populare soluții pentru gestionarea acțiunilor asincronizate în Redux. Astfel de abordări sunt utile atunci când trebuie să urmăriți starea unei cereri în statul dvs.

Un tipar pe care l-am văzut destul de des și care se folosește thunks este următorul:

import {
  FETCH_DATA_ERROR,
  FETCH_DATA_PENDING,
  FETCH_DATA_SUCCESS,
} from 'constants/actionTypes';

function fetchMyDataError(error) {
  return {
    type: FETCH_DATA_ERROR,
    payload: error,
  };
}

function fetchDataPending() {
  return { type: FETCH_DATA_PENDING };
}

function fetchMyDataSuccess(response) {
  return {
    type: FETCH_DATA_SUCCESS.
    payload: response,
  };
}

function fetchData() {
  return (dispatch) => {
    dispatch(fetchDataPending());
      
    fetch('https://my-api.com/my-data')
      .then(res => res.json())
      .then(data => dispatch(fetchMyDataSuccess(data)))
      .catch(err => dispatch(fetchMyDataError(err)));
  };
}

După cum puteți vedea, am scris o cantitate bună de cod. Acest exemplu poate fi simplificat și tratat cu o singură funcție. Oricum ar fi, în curând se va simți foarte repetitiv și obositor, mai ales dacă trebuie să urmăriți durata de viață a fiecărei cereri asincronizate din aplicația dvs. O astfel de verbozitate nu ajută la cazanul necesar pentru o aplicație care utilizează Redux.

Atunci când un model sau un bloc de cod este folosit din nou și din nou, este o bună practică să îl extrageți într-o funcție. Acest lucru va abstra logica acestuia și necesită doar cea mai mică cantitate de date pentru a „funcționa”. Atunci am început să mă joc cu ideea de a-mi scrie propriul middleware. redux-slim-async mă ajută să omit codul boilerplate și să asigur un control excelent cu un API mic. Să vedem acum exemplul anterior cu noul middleware:

import {
  FETCH_DATA_PENDING,
  FETCH_DATA_SUCCESS,
  FETCH_DATA_ERROR,
} from 'constants/actionTypes';

function fetchData() {
  return {
    types: [
      FETCH_DATA_PENDING,
      FETCH_DATA_SUCCESS,
      FETCH_DATA_ERROR,
    ],
    callAPI: fetch(‘https://my-api.com/my-data')
      .then(res => res.json()),
  }
}

Toate acele funcții incomode au dispărut și ale noastre fetchData este acum minim – destul de îngrijit! ?

Acum să mergem mai departe și să construim o versiune mai mică a unui astfel de middleware. Ne va ajuta să înțelegem funcționarea interioară a acestuia și, hei, vei putea să-ți construiești propriul!

Crearea unui middleware

Permiteți-mi să vă arăt imediat codul pentru acest mic middleware. Vei vedea că nu este atât de copleșitor pe cât ai putea crede.

function createSlimAsyncMiddleware({ dispatch, getState }) {
  return next => action => {
    const {
      types,
      callAPI,
      shouldCallAPI = () => true,
    } = action;
      
    if (!actionIsValid(action)) next(action);
    if (!shouldCallAPI(getState())) {
      return Promise.resolve(getState());
    }
      
    const [pendingType, successType, errorType] = types;
      
    dispatch({ type: pendingType });
      
    return callAPI()
      .then(response => {
        dispatch({
          type: successType,
          payload: response,
        });
        
        return Promise.resolve(getState());
      })
      .catch(error => {
        dispatch({
          type: errorType,
          payload: error,
        });
        
        return Promise.reject(error);
     });
  };
}

Stai o secundă … asta e? Absolut!

Să mergem pe rând. Acest middleware este o funcție care returnează o funcție, care returnează o funcție care returnează un Promise. Pe cât de funky pare, veți găsi că este mult mai simplu decât pare.

Funcția noastră middleware primește un obiect cu două câmpuri: dispatch și getState. Acestea sunt parametrii numiți furnizat de Redux.

  • dispatch: după cum sugerează și numele, aceasta este ceea ce folosim pentru a trimite o acțiune. Ne va oferi puterea de a gestiona acțiunile din middleware
  • getState: aceasta este o funcție care returnează starea curentă la un moment dat. Acest lucru poate fi util dacă dorim să returnăm starea actualizată după ce o acțiune a fost expediată

Pe prima linie avem o funcție cu un singur argument argument cu câmpuri dispatch și getState.

Pe a doua linie returnăm o funcție care ia un argument numit next. O astfel de funcție returnează o funcție care ia un action și face ceva. Mai multe despre asta mai târziu. Dar ce este next pentru ? De ce ne așteptăm să returnăm o funcție care returnează o funcție care face ceva?

Ce face Redux sub capotă Compune mijlocul, astfel încât fiecare să aibă o referință la … next unu! Numele ajută foarte mult să-l facă intuitiv. Înfășurăm Redux oficial dispatch funcționează cu middleware-ul nostru. Aceasta construiește o conductă prin care trebuie să treacă o acțiune.

Amintiți-vă că nu trebuie să sunați next(action), dar trebuie să faceți acest lucru dacă nu doriți să blocați procesul de expediere (vom vedea un caz specific în middleware-ul nostru).

Cum sa creati cu usurinta primul dvs middleware
O diagramă de flux care explorează conducta middleware într-un mod simplificat

În cazul nostru, este util, deoarece nu vrem să interceptăm fiecare acțiune, doar cele valabile pentru middleware-ul nostru. Pentru simplitate, am adăugat un cec numit actionIsValid. Această funcție necesită un action ca argument și returnează un boolean. Booleanul returnat reprezintă validitatea acestei acțiuni pentru middleware-ul nostru.

actionisValid este un loc bun pentru a verifica erorile și throw dacă este necesar. Dacă nu este valid, atunci voi folosi referința noastră la next middleware și transmite-i acțiunea. Altfel putem folosi în cele din urmă acțiunea și „face ceva” (diagrama de mai sus reprezintă o versiune simplificată a acestei logici).

Restul middleware-ului este destul de intuitiv. Verificăm validitatea acțiunii pentru a determina dacă solicitarea noastră asincronă trebuie să se desfășoare sau nu.

shouldCallAPI este un parametru al API-ului nostru de middleware. Având în vedere starea, returnează un boolean care determină dacă cererea noastră trebuie să fie executată sau nu. Middleware-ul oferă o valoare implicită (o funcție care revine true ). Dacă nu trebuie să facem apelul API, atunci ne întoarcem Promise.resolve. Astfel putem folosi .then sau async/await cu privire la orice acțiune asincronă care trece prin middleware-ul nostru.

const [pendingType, successType, errorType] = types;

Următorul pas este determinarea acțiunii type câmp transmis ca parametru. Folosim matrice destructurare să ne demontăm types parametru matrice.

dispatch({ type: pendingType });

Acum putem folosi în cele din urmă dispatch metodă. Aceasta trimite o acțiune Redux așa cum ați face în mod normal. O astfel de acțiune reprezintă starea „în așteptare” a cererii noastre de sincronizare.

return callAPI()
  .then(response => {
    dispatch({
      type: successType,
      payload: response,
    });
    
    return Promise.resolve(getState());
  })
  .catch(error => {
    dispatch({
      type: errorType,
      payload: error,
    });
    
    return Promise.reject(error);
  });

În sfârșit avem ultimul return afirmație. Aici efectuăm apelul API și, pe baza modului în care Promise rezolvă, trimitem și returnăm valori diferite.

  • Succes: dat fiind răspunsul din API, trimitem o acțiune de succes. Sarcina utilă este răspunsul la cerere. Imediat după aceea, ne întoarcem a Promise care se rezolvă cu starea actualizată a aplicației noastre. Acest lucru ne permite să folosim .then(updatedState => …do something)
  • Eroare: dacă Promise respinge apoi trimitem o acțiune de eroare. În acest caz, sarcina utilă este eroarea în sine.

Asta e! După cum s-a arătat mai sus, putem crea acțiuni și le putem utiliza după cum urmează:

// Our Action

function fetchData() {
  return {
    types: [
      FETCH_DATA_PENDING,
      FETCH_DATA_SUCCESS,
      FETCH_DATA_ERROR,
    ],
    shouldCallAPI: state => state.dataArr.length === 0,
    callAPI: () =>
      fetch('https://my-api.com/my-data').then(res => res.json()),
  }
}

// Inside the component

class MyComponent extends Component {
  componentDidMoun() {
    this.props.fetchData()
      .then(state => {
        console.log('updated state after async action:', state);
      })
      .catch(err => {
        console.log('an error occured');
      });
  }
  
// Rest of the component omitted...

}

În acest caz simplu, preluăm date numai dacă matricea noastră de date este goală. Apoi înregistrăm starea actualizată după cerere sau un mesaj de eroare dacă Promise respinge ..

Concluzie

Crearea programelor de mijloc Redux este intuitivă. Aveți acces la dispeceratul magazinului și la getState funcţie. Folosiți-le pentru a accesa cea mai recentă stare a aplicației dvs. sau pentru a trimite acțiuni.

De asemenea, trebuie să vă amintiți să utilizați next atunci când este necesar și asigurați-vă că nu blocați conducta de expediere. În cazul nostru, dacă nu am sunat next(action) , orice acțiune care nu a fost valabilă pentru middleware-ul nostru ar fi practic aruncată ⚠️ !!

Unele detalii de implementare au fost omise aici pentru simplitate. Dacă doriți să săpați puțin mai adânc, nu ezitați să explorați redux-slim-async middleware aici.

Dă-i un ⭐️ dacă îți place! Am construit acest middleware și îl folosesc în prezent în producție pentru a evita o mulțime de boilerplate. Simțiți-vă liber să încercați și să oferiți feedback oricând. Iată o altă resursă valoroasă pentru a explora și mai mult mijlocul, programul redux docs!

Poți să mă urmărești și pe twitter @SuperGabry