Introducere în React, partea 3

Publicat: 2020-07-30

Săptămâna trecută am văzut că o componentă React nu este altceva decât reprezentarea grafică a (parte a) stării aplicației noastre. În exemplul pe care l-am implementat, o componentă arăta valoarea unui contor împreună cu două butoane care permiteau creșterea sau scăderea acestei valori. Trucul a fost simplu: elementele de props ale unei componente pot fi atât date, cât și funcții.

Până acum, cele două idei principale pe care le-am învățat din partea 1 și partea 2 a acestui tutorial sunt:

  • O componentă React este o funcție pură care primește un set de proprietăți și generează codul HTML necesar pentru redarea acestuia.
  • Proprietățile pe care le primește o componentă React pot fi (a) datele pe care ar trebui să le afișeze în interfața de utilizare și (b) funcții care, odată conectate ca apeluri înapoi la evenimentele DOM, modifică starea aplicației noastre.

Ei bine, în această a treia parte ne vom extinde puțin mai mult cunoștințele despre această arhitectură și vom învăța cum să folosim magazinele WordPress . Cu ele, putem defini starea aplicației noastre într-un mod curat, testabil și independent de UI.

Extinderea exemplului nostru

În a doua parte a acestui tutorial am creat un contor cu câteva butoane pentru a crește sau a micșora valoarea acestuia. Pentru a face acest lucru, am adăugat o variabilă mutabilă în fișierul index.js împreună cu o funcție pentru a o modifica. Componenta noastră de contor :

 export const Counter = ( { value, onIncrease, onDecrease } ) => ( <div> <div>Counter: <strong>{ value }</strong></div> <button onClick={ onIncrease }>+</button> <button onClick={ onDecrease }>-</button> </div> );

ar putea primi apoi toate recuzita de care avea nevoie:

 // Import dependencies import { render } from '@wordpress/element'; import { Counter } from './components/counter'; // Store value let value = 0; function setValue( newValue ) { value = newValue; } // Render component in DOM const wrapper = document.getElementById( 'react-example-wrapper' ); render( <Counter value={ value } onIncrease={ () => setValue( value + 1 ) } onDecrease={ () => setValue( value - 1 ) } />, wrapper );

Ei bine, astăzi vom învăța cum putem implementa corect și mai bine managementul statului. Dar mai întâi, permiteți-mi să țintesc un exemplu mai complex: să ne extindem aplicația astfel încât să poată avea mai multe contoare simultan și să vedem cum le putem gestiona pe toate folosind un magazin WordPress. Rezultatul final (pe care îl vom implementa în postarea următoare) va arăta similar cu acesta:

Contoare multiple cu React și Redux
Contoare multiple implementate cu componente React și un magazin WordPress bazat pe Redux.

Definirea stării aplicației noastre

Când vă confruntați cu o problemă ca aceasta, primul lucru la care ar trebui să vă gândiți este modul în care veți gestiona starea aplicației dvs. Și, presupunând că puteți citi și scrie doar starea folosind funcții, cel mai simplu mod de a face acest lucru este să vă gândiți la API-ul său: adică la funcțiile care vă permit să o interogați și să o actualizați . În cazul nostru, aș putea sugera:

  • Adăugarea unui nou contor
  • Se șterge contorul x
  • Setați valoarea contorului x
  • Obțineți o listă cu toate contoarele pe care le are aplicația noastră
  • Obțineți valoarea contorului x

dar s-ar putea să doriți să utilizați o abordare diferită (de exemplu, s-ar putea să doriți să creați o funcție pentru creșterea unui anumit numărător și o funcție diferită pentru decrementarea acestuia).

Odată ce ați definit această interfață, ar trebui să vă gândiți la informațiile de care aveți nevoie pentru a ține evidența acestei stări și, în special, la structura de date pe care o veți folosi pentru a face acest lucru. În exemplul nostru, dorim să ținem evidența mai multor contoare și, pentru fiecare contor x , dorim să cunoaștem valoarea acestuia. Ce structuri de date ne-ar putea ajuta în acest demers?

În funcție de ce este acest x , este posibil să doriți să utilizați o structură de date sau alta. Dacă, de exemplu, x este indexul contorului (adică doriți să puteți prelua valoarea primului , al doilea sau al treilea contor), o matrice de numere ar putea fi suficientă. Dacă doriți ca x să fie un identificator unic de contor, este posibil să doriți să utilizați o abordare diferită. De exemplu, ați putea dori să utilizați un dicționar cu perechi id/valoare:

 const counters = { ae13a: 0, f18bb: 3, e889a: 1, 8b1d3: -5, };

sau o serie de obiecte:

 const counters = [ { id: 'ae13a', value: 0 }, { id: 'f18bb', value: 3 }, { id: 'e889a', value: 1 }, { id: '8b1d3', value: -5 }, ];

Dacă definiți mai întâi interfața (setters și getters, dacă doriți) pentru a manipula și accesa starea aplicației dvs., atunci modul în care implementați starea în sine este o cutie neagră. Aceasta înseamnă că puteți schimba magazinul în sine în orice moment și lucrurile vor funcționa conform așteptărilor, atâta timp cât mențineți API-ul.

Crearea unui magazin bazat pe Redux cu WordPress

Modulul de date al WordPress vă permite să definiți unul sau mai multe magazine pentru a gestiona starea aplicației dvs. Pentru a crea un magazin nou folosind acest pachet, trebuie pur și simplu să utilizați funcția registerStore cu următoarele argumente:

  1. Un nume care vă identifică în mod unic magazinul
  2. Un obiect de selectors cu toți getters pentru a prelua date din magazin
  3. Un obiect de actions cu funcție care declanșează solicitări de actualizare (vom vorbi despre ce înseamnă asta mai târziu în această postare)
  4. O funcție de reducer care este responsabilă pentru actualizarea stării atunci când sunt declanșate anumite acțiuni

Să vedem un exemplu real, nu? Continuând cu exemplul de săptămâna trecută, creați un nou folder store în src . Apoi creați un fișier index.js în el cu următorul cod:

 // Load dependencies import { registerStore } from '@wordpress/data'; import reducer from './reducer'; import * as actions from './actions'; import * as selectors from './selectors'; registerStore( 'react-example/counters', { actions, reducer, selectors, } );

Fragmentul este destul de simplu, nu-i așa? Pur și simplu importăm funcția registerStore despre care vorbeam și câteva dependențe pe care nu le-am creat încă ( reducer , actions și selectors ) și înregistrăm noul magazin. Observați cum ne-am denumit magazinul pentru a ne asigura că este unic: am combinat numele pluginului nostru ( react-example ) cu un cuvânt care definește despre ce este vorba în magazin ( counters ). Ușor de păsărit!

Acțiuni într-un magazin

Unul dintre lucrurile de care (în principiu) are nevoie fiecare magazin este un set de funcții care ne permit să-i modificăm starea. În exemplul nostru, acțiunile vor fi în src/store/actions.js :

 export function addCounter( counterId ) { return { type: 'ADD_COUNTER', counterId, }; } export function removeCounter( counterId ) { return { type: 'REMOVE_COUNTER', counterId, }; } export function setCounterValue( counterId, value ) { return { type: 'SET_COUNTER_VALUE', counterId, value, }; }

După cum era de așteptat, magazinul nostru are trei acțiuni pentru a-și actualiza starea:

  • addCounter : o funcție care adaugă contoare noi în magazinul nostru. Singurul argument necesar este ID-ul noului contor.
  • removeCounter : o funcție care elimină un contor existent. Din nou, singurul argument necesar este ID-ul contorului pe care vrem să-l eliminăm.
  • setCounterValue : o funcție care setează o nouă valoare unui contor dat. Evident, această funcție are două argumente: ID-ul contorului de actualizat și noua sa valoare.

Acum, dacă te uiți îndeaproape la fiecare acțiune, s-ar putea să fii surprins: niciuna dintre aceste acțiuni nu pare să actualizeze nimic. În schimb, ei returnează obiecte. Ce se petrece aici?

În Redux (și magazinele WordPress se bazează pe Redux), acțiunile nu modifică direct un magazin. În schimb, generează un obiect care semnalează o „cerere de actualizare”. Aceste solicitări urmează întotdeauna același model: sunt un obiect cu un atribut de type care identifică în mod unic cererea și câte proprietăți suplimentare sunt necesare pentru a aplica cu succes actualizarea solicitată.

Deci, să vedem acum cum se poate actualiza de fapt starea...

Implementarea reductorului pentru a actualiza starea unui magazin

Dacă acțiunile magazinului reprezintă doar o solicitare de actualizare, avem nevoie de cineva sau de ceva care să actualizeze starea magazinului nostru atunci când o astfel de solicitare este expediată. Pentru asta este folosit un reductor.

Un reductor este o funcție care preia starea curentă a aplicației noastre și o acțiune pe care cineva a trimis-o și actualizează starea aplicând actualizarea solicitată .

În secțiunea anterioară am văzut că magazinul nostru are trei acțiuni, așa că reductorul nostru trebuie să poată aplica fiecare dintre ele:

 import { omit } from 'lodash'; export default function reducer( state = {}, action ) { switch ( action.type ) { case 'ADD_COUNTER': return { ...state, [ action.counterId ]: 0, }; case 'REMOVE_COUNTER': return omit( state, action.counterId ); case 'SET_COUNTER_VALUE': return { ...state, [ action.counterId ]: action.value, }; } return state; }

După cum puteți vedea, reductorul primește state anterioară (care, apropo, implicit este obiectul gol {} ) și acțiunea expediată cu informațiile de actualizare a stării. Corpul reductorului este destul de simplu:

  • Începe cu o instrucțiune switch pentru a discerne tipul de actualizare ( action.type ) pe care ar trebui să o rulăm:
    • Dacă este ADD_COUNTER , generează un nou obiect de state cu o nouă cheie action.counterId cu valoarea contorului setată la 0 .
    • Dacă este REMOVE_COUNTER , generează un nou obiect de state fără cheia action.counterId .
    • Dacă este SET_COUNTER_VALUE , generează un nou obiect de state în care valoarea din action.counterId este acum setată la action.value .

Cel mai important lucru aici este să realizați că un reductor este (și trebuie să fie) o funcție pură . Aceasta înseamnă că orice modificare pe care o facem stării actuale implică construirea unui nou stat . Prin urmare, sub nicio formă nu trebuie să modificați starea anterioară.

Selectoare într-un magazin

Acum că știți cum să actualizați starea magazinului dvs., tot ce trebuie să învățați este cum să îl interogați. Doar definiți un selectors.js cu funcțiile de interogare de care aveți nevoie:

 export function getCounterIds( state ) { return Object.keys( state ); } export function getCounterValue( state, counterId ) { return state[ counterId ]; }

si asta e! Destul de evident, nu? Selectorii de magazin sunt funcții care iau (cel puțin) un argument ( state magazinului) și returnează o anumită valoare . Evident, selectorii pot avea mai multe argumente dacă trebuie să returnați o anumită valoare din magazinul dvs.

În cazul nostru, de exemplu, am creat doi selectori:

  • getCounterIds returnează o matrice de identificatori de contor. Deoarece am implementat magazinul folosind un dicționar/obiect, pur și simplu suntem interesați să returnăm Object.keys .
  • getCounterValue returnează valoarea specifică a unui contor dat. Această funcție ia două argumente ( state curentă a aplicației noastre și ID-ul contorului de care suntem interesați) și returnează valoarea solicitată.

Cum să testăm magazinul nostru

Pentru a testa dacă magazinul funcționează corect, deschideți fișierul src/index.js și import -l:

 // Import dependencies import { render } from '@wordpress/element'; import './store'; import { Counter } from './components/counter'; ...

Apoi, transpilați codul (folosind npm run build ) și accesați browserul. Deschideți Instrumentele pentru dezvoltatori și utilizați consola JavaScript pentru a introduce câteva comenzi:

 dispatch = wp.data.dispatch( 'react-example/counters' ); select = wp.data.select( 'react-example/counters' ); dispatch.addCounter( 'a' ); dispatch.addCounter( 'b' ); dispatch.addCounter( 'c' ); dispatch.setCounterValue( 'a', 3 ); select.getCounterIds(); // Array(3) [ "a", "b", "c" ] select.getCounterValue( 'a' ); // 3

Folosind funcțiile de dispatch și select ale WordPress, veți putea vedea că magazinul funcționează conform așteptărilor. Și un sfat bonus: există o extensie atât pentru Firefox, cât și pentru Chrome numită Redux DevTools , care vă permite să vedeți corect magazinele Redux:

Redux DevTools în Firefox
Extensia Redux DevTools pentru Firefox și Chrome vă permite să verificați cu ușurință starea magazinelor Redux.

Pasii urmatori

Adevărul este că postarea de astăzi a fost puțin mai lungă decât mă așteptam, așa că nu vom putea vedea cum putem folosi acest magazin pentru a ne alimenta UI (încă). Dar sper că explicația te va ajuta să înțelegi cum funcționează magazinele WordPress și cum le poți folosi pentru a gestiona starea pluginurilor tale.

Săptămâna viitoare vom merge mai departe cu exemplul de astăzi și vom conecta componentele noastre React la magazin, astfel încât (a) ceea ce vedem în UI să fie alimentat de starea stocată în magazin și (b) interacțiunile utilizatorului cu UI să actualizeze magazinul ( precum și interfața de utilizare în sine).

Dar, doar pentru a vă asigura că vă simțiți confortabil cu tot ce v-am arătat astăzi, permiteți-mi să vă propun niște teme: modificați magazinul pe care l-am implementat astăzi, astfel încât datele să fie stocate după cum urmează:

 const counters = [ { id: 'ae13a', value: 0 }, { id: 'f18bb', value: 3 }, { id: 'e889a', value: 1 }, { id: '8b1d3', value: -5 }, ];

și nu ne mai face un dicționar. Rețineți că poate fi necesar să actualizați selectoarele, acțiunile și reductorul pentru ca acesta să funcționeze! Voi împărtăși o soluție săptămâna viitoare

Imagine prezentată de Annie Theby pe Unsplash.