de Fabian Terh

Un ghid cuprinzător pentru verificarea tipurilor React, Redux și React-Redux with Flow

Un ghid cuprinzator pentru verificarea tipurilor React Redux si React

Acest articol este împărțit în 4 secțiuni:

  1. Tastați verificarea acțiunilor Redux, a creatorilor de acțiuni și a reducătorilor
  2. Instalarea definițiilor bibliotecii Flow
  3. Tipul verificării stării aplicației
  4. Tastați verificarea magazinului Redux și expediere

Deși există o grămadă de ghiduri în prima secțiune care sunt extrem de utile, am găsit doar puține articole pe 3 și 4. După o lungă sesiune de căutări Google, scufundându-mă în codul sursă și încercări și erori, am decis să pun împreună ceea ce am învățat și scrieți acest tutorial ca un ghid unic pentru a tasta verificarea aplicației React + Redux + React-Redux cu Flow.

1. Tastați verificarea acțiunilor Redux, a creatorilor de acțiuni și a reducătorilor

Acțiuni

Acțiunile Redux sunt în esență obiecte Javascript vanilate cu un obiect obligatoriu type proprietate:

// This is an action{  type: 'INCREASE_COUNTER',  increment: 1}

Urmând cele mai bune practici, vă recomandăm să le definiți și să le utilizați constante de tip acțiune in schimb. Dacă da, fragmentul de mai sus ar arăta probabil ca așa:

const INCREASE_COUNTER = 'INCREASE_COUNTER';
// This is an action{  type: INCREASE_COUNTER,  increment: 1}

Verificarea tipurilor este ușoară (avem de-a face cu JavaScript obișnuit aici):

type $action = {  type: 'INCREASE_COUNTER',  increment: number};

Rețineți că nu puteți înlocui tip șir literal cu constanta INCREASE_COUNTER. Acesta este un prescripţie a Fluxului în sine.

Creatori de acțiune

Întrucât creatorii de acțiuni sunt doar funcții care returnează acțiuni, avem în continuare de-a face cu Javascript obișnuit. Așa poate arăta un creator de acțiuni verificat de tip:

function increaseCounter(by: number): $action {  return {    type: INCREASE_COUNTER, // it's okay to use the constant here    increment: by  };}

Reductoare

Reductoarele sunt funcții care gestionează acțiunile. Ei primesc un stat și o acțiune și returnează noul stat. În acest moment, este important să vă gândiți la cum va arăta starea dvs. (forma stării). În acest exemplu foarte simplu, forma stării cuprinde doar o singură cheie counter care preia o number valoarea tipului:

// State shape{  counter: <number>}

Și astfel reductorul dvs. ar putea arăta astfel:

const initialState = { counter: 0 };
function counter(state = initialState, action) {  switch (action.type) {    case INCREASE_COUNTER:      return Object.assign({}, state, {        counter: action.increment + state.counter      });        default:      return state;  }};

Notă: În acest exemplu particular, Object.assign({}, state, { ... }) este redundant deoarece magazinul constă doar din 1 pereche cheie / valoare. Aș putea întoarce la fel de ușor ultimul argument asupra funcției. Cu toate acestea, am inclus implementarea completă pentru corectitudine.

Tastarea stării și a reductorului este suficient de simplă. Aici este tastat versiunea fragmentului de mai sus:

type $state = {  +counter: number};
const initialState: $state = { counter: 0 };
function counter(  state: $state = initialState,  action: $action): $state {    switch (action.type) {    case INCREASE_COUNTER:      return Object.assign({}, state, {        counter: action.increment + state.counter      });        default:      return state;  }};

Instalarea definițiilor bibliotecii Flow

curgere definiții bibliotecii (sau libdefs) oferă definiții de tip pentru module terță parte. În acest caz, folosim React, Redux și React-Redux. În loc să tastați manual aceste module și funcțiile lor, puteți instala definițiile de tip folosind flow-typed:

npm install -g flow-typed
// Automatically download and install all relevant libdefsflow-typed install
// Orflow-typed install <package>@<version> // e.g. redux@4.0.0

Definițiile bibliotecii sunt instalate în flow-typed folder, care permite Flow să funcționeze din cutie fără nicio altă configurație (Detalii).

Tipul verificării stării aplicației

Anterior, am tastat deja statul astfel:

type $state = {  +counter: number};

În timp ce acest lucru funcționează pentru un exemplu simplu precum cel de mai sus, se defectează odată ce starea dvs. devine semnificativ mai mare. Ar trebui să editați manual type $state de fiecare dată când introduceți un reductor nou sau îl modificați pe unul existent. De asemenea, nu ați dori să păstrați toate reductoarele în același fișier. Ceea ce doriți să faceți în schimb este să vă refactorizați reductoarele în module separate și să utilizați Redux combineReducers funcţie.

Deoarece accentul acestui articol este pe verificarea tipului o aplicație React / Redux / React-Redux și nu pe construirea unuia, voi presupune că sunteți familiarizat cu combineReducers funcţie. Dacă nu, mergeți la Tutorialul Redux pentru a afla totul despre asta.

Să presupunem că introducem o nouă pereche de acțiune / reductor într-un modul separat:

// playSong.js
export const PLAY_SONG = 'PLAY_SONG';
// Typing the actionexport type $playSongAction = {  type: 'PLAY_SONG',  song: ?string};
// Typing the action creatorexport function playSong(song: ?string): $playSongAction {  return {    type: PLAY_SONG,    song: song  };};
// Typing arg1 and the return value of the reducer [*1]export type $song = ?string;
// Typing the state [*1]export type $songState = {  +song: $song};
// [*1][*2]const initialValue: $song = null;
// Typing the reducer [*1][*3]function song(  state: $song = initialValue,  action: $playSongAction): $song {    switch (action.type) {    case PLAY_SONG:      return action.song;        default:      return state;  }};
[*1]: Dacă folosim combineReducers funcție, este important să rețineți că reductorul dvs. nu ar trebui să mai returneze statul, ci mai degrabă valoare pentru cheia din stat. În această privință, cred Tutorialul Redux este puțin lipsit de claritate, deoarece nu afirmă acest lucru în mod explicit, deși este clar din exemplul de fragmente de cod.

[*2]: Reductorii nu au voie să se întoarcă undefined, așa că trebuie să ne mulțumim cu null.

[*3]: Deoarece reductorul nu mai primește și nu returnează o stare sub forma { song: string }, ci mai degrabă valoare la song cheie în obiectul de stare, trebuie să schimbăm tipurile primului său argument și să returnăm valoarea $songState la $song.

Modificăm și refactorizăm increaseCounter de asemenea:

// increaseCounter.js
export const INCREASE_COUNTER = 'INCREASE_COUNTER';
export type $increaseCounterAction = {  type: 'INCREASE_COUNTER',  increment: number};
export function increaseCounter(by: number): $action {  return {    type: INCREASE_COUNTER,    increment: by  };};
export type $counter = number;
export type $counterState = {  +counter: $counter};
const initialValue: $counter = 0;
function counter(  state: $counter = initialValue,  action: $increaseCounterAction): $counter {    switch (action.type) {    case INCREASE_COUNTER:      return action.increment + state;        default:      return state;  }};

Acum avem 2 perechi de acțiune / reductor.

Putem crea un nou State tastați pentru a stoca tipul stării aplicației noastre:

export type State = $songState & $counterState;

Acesta este un flux tip intersecțieși este echivalent cu:

export type State = {  song: $song,  counter: $counter};

Dacă nu doriți să creați $songState și $counterState numai pentru utilizare la intersectarea stării aplicației State, este și foarte bine – mergeți cu a doua implementare.

Tastați verificarea magazinului Redux și expediere

Am constatat că Flow raporta erori în containerele mele (în contextul paradigma container / componentă).

Could not decide which case to select. Since case 3 [1] may work but if it doesn't case 6 [2] looks promising too. To fix add a type annotation to dispatch [3].

Acest lucru a fost legat de mine mapDispatchToProps funcţie. Cazurile 3 și 6 sunt următoarele:

// flow-typed/npm/react-redux_v5.x.x.js
// Case 3declare export function connect<  Com: ComponentType<*>,  A,  S: Object,  DP: Object,  SP: Object,  RSP: Object,  RDP: Object,  CP: $Diff<$Diff<ElementConfig<Com>;, RSP>, RDP>  >(  mapStateToProps: MapStateToProps<S, SP, RSP>,  mapDispatchToProps: MapDispatchToProps<A, DP, RDP>): (component: Com) => ComponentType<CP & SP & DP>;
// Case 6declare export function connect<  Com: ComponentType<*>,  S: Object,  SP: Object,  RSP: Object,  MDP: Object,  CP: $Diff<ElementConfig<Com>, RSP>  >(  mapStateToProps: MapStateToProps<S, SP, RSP>,  mapDispatchToPRops: MDP): (component: Com) => ComponentType<$Diff<CP, MDP> & SP>;

Nu știu de ce apare această eroare. Dar, după cum sugerează eroarea, tastarea dispatch o remediază. Și dacă tastăm dispatch, am putea la fel de bine să tastăm store de asemenea.

Nu am putut găsi prea multe documente despre acest aspect al tastării unei aplicații Redux / React-Redux. Am învățat scufundându-mă în libdefs și uitându-mă la cod sursă pentru alte proiecte (deși un proiect demonstrativ). Dacă aveți informații, vă rugăm să ne anunțați pentru a putea actualiza această postare (cu atribuire adecvată, desigur).

Între timp, am constatat că funcționează:

import type {  Store as ReduxStore,  Dispatch as ReduxDispatch} from 'redux';
// import any other variables and types you may need,// depending on how you organized your file structure.
// Reproduced from earlier onexport type State = {  song: $song,  counter: $counter};
export type Action =   | $playSongAction  | $increaseCounterAction
export type Store = ReduxStore<State, Action>;
export type Dispatch = ReduxDispatch<Action>;

Trecând la modulele de containere, puteți continua să tastați mapDispatchToProps după cum urmează: const mapDispatchToProps = (dispatch: Dispatch) => { ... };

Înfășurându-se

Aceasta a fost o postare destul de lungă și sper că ți s-a părut utilă. Am scris-o parțial din cauza lipsei de resurse referitoare la secțiunile ulterioare ale acestui articol (și parțial pentru a-mi organiza gândurile și a consolida ceea ce am învățat).

Nu pot garanta că îndrumările din acest articol respectă cele mai bune practici sau sunt conceptuale solide sau chiar 100% corecte. Dacă observați erori sau probleme, vă rugăm să ne anunțați!