This shows you the differences between two versions of the page.
pw:laboratoare:08 [2021/04/18 11:43] alexandru.hogea |
pw:laboratoare:08 [2021/04/25 22:35] (current) alexandru.hogea [Exercitii] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Laboratorul 08: Introducere in ReactJS ===== | + | ===== Laboratorul 08: React Hooks ===== |
- | ==== 1. Ce este Single Page Application (SPA)? ==== | + | ===== 1. React Hooks ===== |
- | Un SPA sau o aplicatie cu o singura pagina, este o aplicatie (sau un website) care interactioneaza cu browserul web prin rescrierea dinamica, a DOM-ului current, cu date noi generate fie din client, fie din server. In acest caz, browserul nu este nevoit sa incarce o pagina noua. | + | **[[https://reactjs.org/docs/hooks-overview.html|Hooks]]** sunt o noua adaugare in **React 16.8**. Va permit sa utilizati functiile de stare si alte functii React, fara a scrie o clasa. Avantajul este mai putin cod scris pentru a obtine aceeasi functionalitate. |
- | ==== 2. Ce este JSX ? ==== | + | === 1.1 useState() === |
- | Considerati urmatoarea linie | + | [[https://reactjs.org/docs/hooks-state.html|useState]] este un hook de React care va permite sa interactionati cu starea unei componente. Acesta va intoarce un vector format din 2 elemente: |
- | {{ :pw:laboratoare:screenshot_1.png?nolink |}} | + | * Variabila de stare |
+ | * Functia care va modifica variabila de stare | ||
- | Aceasta sintaxa se numeste JSX, iar denumirea a pornit de la Syntax Extension to JavaScript. Aceasta sintaxa este folosita in React pentru a descrie cum va arata o componenta. JSX se poate sa va duca cu gandul la HTML, diferenta este ca JSX vine si cu toate functionalitatile JavaScript. | + | Functia **useState** primeste ca parametru valoarea initiala a starii |
- | In exemplul de mai jos, declaram o variabila name, iar ulterior o folosim in interiorul JSX fiind pusa intre paranteze acolade. | + | <code javascript> |
- | {{ :pw:laboratoare:screenshot_2.png?nolink |}} | + | const [myState, myFunctionToChangeState] = useState(initialValue); |
+ | </code> | ||
- | De asemenea, puteti sa puneti orice cod JavaScript valid in interiorul parantezelor acolade in JSX. | + | <note tip>Atentie, valoarea initiala trebuie sa reflecte tipul de stare pe care vreti sa il retineti (string, numar, obiect, etc...)</note> |
- | {{ :pw:laboratoare:screenshot_3.png?nolink |}} | + | <note tip>O componenta poate apela **useState** de mai multe ori, pentru a retine stari diferite</note> |
- | JSX poate sa fie folosit si in contextul unor expresii, precum | + | {{:pw:laboratoare:mai_multe_stari.png?800|}} |
- | {{ :pw:laboratoare:screenshot_4.png?nolink |}} | + | |
- | Tag-urile JSX pot contine si mai multi copii. | + | Acest exemplu reda un contor. Cand faceti clic pe buton, creste valoarea: |
- | {{ :pw:laboratoare:screenshot_5.png?nolink |}} | + | |
- | De asemenea, putem folosi JSX pentru a ne proteja si de eventuale atacuri. | + | {{ :pw:laboratoare:usestate_1.png?nolink |}} |
- | {{ :pw:laboratoare:screenshot_6.png?nolink |}} | + | |
- | **JSX reprezinta obiecte.** | + | <note tip>Observati cum valoarea initiala a starii este 0</note> |
- | Babel se ocupa de compilarea JSX in apeluri React.createElement() | + | |
- | {{ :pw:laboratoare:screenshot_7.png?nolink |}} | + | |
- | ==== 3. Ce este React ? ==== | + | === 1.2 useEffect() === |
- | React este o biblioteca JavaScript pentru construirea de interfete utilizator. Este creat si intretinut de Facebook si de o comunitate de dezvoltatori ( open source ) si desigur si de alte companii individuale. | + | |
- | ==== 4. Crearea unei aplicatii React ==== | + | [[https://reactjs.org/docs/hooks-effect.html|useEffect]] este al doilea hook important din React si va permite propagarea **efectelor colaterale** din React - sau pe scurt, **efecte** - (de exemplu, modificarea DOM-ului, afisarea unor alerte, preluarea unor date, etc...). |
- | Pentru a putea folosi tool-ul oferit de Facebook, avem nevoie sa indeplinim urmatoarele conditii: [[https://nodejs.org/en/ | Node >= 8.10 si npm >= 5.6]]. | + | |
- | Pentru a genera un proiect trebuie sa rulati urmatoarele: | + | Orice actiune care nu se poate efectua in timpul randarii (render), pentru ca poate modifica structura componentei, este considerat efect. |
- | {{ :pw:laboratoare:screenshot_8.png?nolink |}} | + | |
- | Dupa rularea script-ului //npm start// puteti accesa aplicatia accesand link-ul din consola | + | Pe vremuri, cand componentele functionale nu erau atat de folosite si inca se foloseau foarte mult componentele clasa, aceste efecte erau tratate in **ciclurile de viata** ale componentelor. Aceste cicluri de viata nu sunt nimic mai mult decat metode ale claselor de React. Amintim cateva: |
- | <code> | + | * **componentDidMount** - metoda care se executa dupa redarea initiala a componentei |
- | http://localhost:3000 | + | * **componentDidUpdate** - metoda care se executa dupa ce state sau props au fost modificate |
- | </code> | + | * **componentWillUnmount** - metoda care se executa inainte de stergerea componentei - adica este scoasa din DOM |
- | {{ :pw:laboratoare:screenshot_9.png?nolink |}} | + | |
- | ==== 5. Componente ==== | + | {{ :pw:laboratoare:react_events.png?nolink&700 |}} |
- | Componentele va permit sa va impartiti interfata utilizator in "piese" reutilizabile, iar in acelasi timp va puteti gandi la fiecare piesa ca fiind izolata. | + | |
- | Conceptual, componentele sunt asemanatoare cu functiile JavaScript. Acestea accepta intrari (denumite "props") si returneaza elemente React, care descriu ceea ce ar trebui sa apara in interfata. | + | In principal, **efectele** declarate folosind **useEffect** se realizeaza, prin analogie, in **componentDidMount**, **componentDidUpdate** si, daca este declarat un comportament specific si in **componentWillUnmount**. |
- | === 5.1. Functional Components === | + | Mai jos aveti un exemplu de efect care schimba titlul paginii: |
- | Cea mai simpla cale de a defini o componenta este aceea de a scrie o functie JavaScript | + | |
- | {{ :pw:laboratoare:screenshot_10.png?nolink |}} | + | |
- | Aceasta functie este o componenta valida React deoarece accepta un singur obiect "props" (proprietati) cu date si returneaza un element React. Aceste componente poarta denumirea de **function components** deoarece ele reprezinta niste functii JavaScript. | + | {{ :pw:laboratoare:useeffect_1.png?nolink |}} |
- | === 5.2. Class Components === | + | Prin analogie, asa s-ar fi scris folosind clase: |
- | De asemenea, puteti folosi si o [[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes | clasa ES6]] pentru a defini o componenta: | + | |
- | {{ :pw:laboratoare:screenshot_11.png?nolink |}} | + | |
- | Cele doua componente definite mai sus, sunt echivalente din punct de vedere React. | + | {{:pw:laboratoare:react_hook_clasa.png|}} |
- | === 5.3 Adaugarea unei componente in DOM (Rendering a Component) === | + | <note tip> Observati cum logica este duplicata in **componentDidMount** si **componentDidUpdate**. useEffect elimina aceasta duplicare</note> |
- | Pana acum, am intampinat elemente React care reprezentau tag-uri DOM, precum: | + | |
- | {{ :pw:laboratoare:screenshot_12.png?nolink |}} | + | |
- | De asemenea, elementele pot reprezenta si componente definite de utilizator: | + | Exista cazuri cand doriti sa anulati un efect in momentul in care nu mai aveti nevoie de componenta sau cand proprietatile componentei se schimba. Acest proces se numeste **curatare**. In Reactul clasic, acest lucru era scris in metoda **componentWillUnmount**, sau in metoda **componentDidUpdate**. Pentru a face acest lucru in useEffect, este nevoie sa **returnati o functie**. |
- | {{ :pw:laboratoare:screenshot_13.png?nolink |}} | + | |
- | Cand React descopera un element care reprezinta o componenta definita de utilizator, acesta trimite atributele JSX si copiii catre aceasta componenta folosind un singur obiect. Acest obiect poarta numele de "props". | + | Asadar, ce se intampla in corpul functiei useEffect se intampla **de fiecare data** cand componenta se randeaza (render) si ce se returneaza se executa **de fiecare data** cand componenta a terminat de executat render. |
- | In exemplul urmator, codul "randeaza", in pagina, mesajul //Hello, Sara//. | + | De exemplu, sa presupunem ca avem un API de Chat care are doua metode: subscribe si unsubscribe. Aceste doua metode au doi parametri, un id si o functie de callback care seteaza statusul pe online sau offline. Ne dorim sa dam subscribe la inceputul componentei si sa dam unsubscribe, atunci cand parasim componenenta. |
- | {{ :pw:laboratoare:screenshot_14.png?nolink |}} | + | |
- | Componentele create pot fi reutilizate de cate ori dorim. Spre exemplu, putem crea o componenta //App// care afiseaza //Welcome// de mai multe ori, folosind alt nume. | + | Folosind useEffect, acest lucru se poate realiza foarte usor: |
- | {{ :pw:laboratoare:screenshot_15.png?nolink |}} | + | |
- | === 5.4 Stare ( State ) === | + | {{ :pw:laboratoare:useeffect_2.png?nolink |}} |
- | [[https://reactjs.org/docs/state-and-lifecycle.html | State]] reprezinta un obiect JavaScript care pastreaza informatia care influenteaza aspectul sau starea componentei compilate si randate. Diferenta fata de **props** este aceea ca starea este mentinuta in contextul componentei - similar cu definirea unei variabile in interioriul unei functii. | + | |
+ | Pe de alta parte, folosind clasa, codul ar fi aratat astfel: | ||
- | == 5.4.1 Functional Components == | + | {{:pw:laboratoare:react_hook_clasa_2.png|}} |
- | {{ :pw:laboratoare:state_functional.png?nolink&700 |}} | + | |
- | == 5.4.2 Class Components == | + | <note tip>Observati cum ce este returnat in **useEffect** este scris in **componentWillUnmount** si ce este scris in corpul **useEffect** este scris in **componentDidMount**, asa cum am explicat anterior.</note> |
- | {{ :pw:laboratoare:state_class.png?nolink&700 |}} | + | |
- | Cele doua componente prezentate mai sus sunt echivalente, insa exista o mica diferenta: | + | <note important>Codul din ultima coza este un cod ce are bug. Ganditi-va la urmatorul caz: cand **props** este modificat (adica atunci cand parintele ii trimite alta valoare pentru props), componenta **NU** va efectua unsubscribe, chiar daca id-ul s-a modificat. Acest lucru trebuie tratat tot in **componentDidMount**. useEffect elimina aceasta problema, executand codul la fiecare render.</note> |
- | Class Components fac referire la state sau props folosind **this**. | + | Asa ar fi trebuit sa arate codul corect, folosind clase: |
- | <code> | + | |
- | Class components | + | |
- | this.state | + | {{:pw:laboratoare:react_hook_clasa_3.png|}} |
- | this.props | + | |
- | </code> | + | |
- | <code> | + | === 1.3 useReducer() === |
- | Functional Components | + | |
- | state | + | [[https://reactjs.org/docs/hooks-reference.html#usereducer |useReducer]], este o alternativa pentru useState(). Accepta un reducer de tip <code>(state, action) => newState</code> si returneaza state-ul curent si functia dispatch. Dispatch, care primeste o actiune ca argument, declanseaza actualizarea state-ului in functie de actiunea folosita. |
- | props | + | |
- | </code> | + | |
- | === 5.5 Event Handling === | + | Un reducer este o functie care primeste state-ul curent si o actiune si returneaza state-ul modificat, actiunea fiind modalitatea prin care se precizeaza cum se va modifica state-ul. O actiune este un obiect care trebuie obligatoriu sa aiba un tip (**type**). |
- | Gestionarea evenimentelor cu elemente React este foarte asemanatoare cu gestionarea evenimentelor pe elemente DOM. Principalele diferente de sintaxa sunt: | + | |
- | * evenimentele din React sunt camelCase | + | |
- | * pentru gestionarea evenimentelor in React sunt folosite functii, nu string-uri | + | |
- | HTML | + | Este recomandat sa fie folosit daca logica state-ului este mai complexa. |
- | {{ :pw:laboratoare:screenshot_16.png?nolink |}} | + | |
+ | {{ :pw:laboratoare:screenshot_2021-04-25_at_21.39.15.png?nolink&700 |}} | ||
- | React | ||
- | {{ :pw:laboratoare:screenshot_17.png?nolink |}} | ||
- | HTML | ||
- | {{ :pw:laboratoare:screenshot_19.png?nolink |}} | ||
- | React | + | ==== Exercitii ==== |
- | {{ :pw:laboratoare:screenshot_18.png?nolink |}} | + | |
- | === 5.6 Conditional Render === | + | Vom extinde aplicatia creata la laboratorul precedent. |
- | Randarea bazata pe conditii, in React, functioneaza in acelasi mod precum conditionarea in JavaScript. Folositi operatori JavaScript precum [[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else |if]] sau [[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator |conditional operator]] pentru a crea elemente reprezentand starea curenta. | + | |
- | Considerati urmatoarele doua componente: | + | 1. Folosind componentele App si Count, trimiteti valoarea counter-ului si functiile (incrementare, decrementare, reset) folosind props. Cand counter-ul ajunge 0, afisati o alerta. |
- | {{ :pw:laboratoare:screenshot_20.png?nolink |}} | + | <note important>Componenta **Count** si **App** trebuie sa fie componente functionale. Pentru afisarea alertei va trebui sa folositi **useEffect()**. Valoarea lui count va fi definita drept state in App, la fel si functiile de incrementare, decrementare si reset.</note> |
- | Vom implementa o componenta //Greeting// care afiseaza una dintre aceste componente, daca un utilizator este autentificat sau nu. | + | 2. Modificati fisierele de css din extensia **.css** in **.scss**, daca nu ati folosit Sass in laboratorul trecut. |
- | {{ :pw:laboratoare:screenshot_21.png?nolink |}} | + | <note important> |
+ | De asemenea, React permite modularizarea fisierelor SCSS prin denumirea acestora in **numeFisier.module.scss**. | ||
+ | In acest caz, importarea si referentierea claselor css, se face in felul urmator: | ||
- | === 5.7 Liste si chei (key) === | ||
- | In primul rand, sa ne amintim cum transformam listele in JavaScript. | ||
- | {{ :pw:laboratoare:screenshot_22.png?nolink |}} | ||
- | |||
- | Acest cod afiseaza urmatorul rezultate in consola | ||
<code> | <code> | ||
- | [2, 4, 6, 8, 10] | + | numeFisier.module.scss |
- | </code> | + | |
- | == Afisarea mai multor componente == | + | .container { |
- | <code> | + | display: flex; |
- | const numbers = [1, 2, 3, 4, 5]; | + | justify-content: space-between; |
- | const listItems = numbers.map((number) => | + | align-items: center; |
- | <li>{number}</li> | + | flex-direction: row; |
- | ); | + | padding: 1rem 0; |
- | </code> | + | font-size: 1em; |
+ | font-weight: 700; | ||
+ | [...] | ||
+ | } | ||
- | {{ :pw:laboratoare:screenshot_23.png?nolink |}} | + | import styles from './numeFisier.module.scss'; |
- | Cand rulam acest cod, in consola va fi afisat un mesaj de atentionare spunandu-ne ca pentru fiecare //li// (list item) trebuie sa furnizam o cheie unica. | + | <div className={styles.container}> |
- | {{ :pw:laboratoare:screenshot_24.png?nolink |}} | + | [...] |
+ | </div> | ||
+ | </code> | ||
- | Pentru a rezolva aceasta problema, trebuie sa introducem urmatoarea modificare: | + | In cazul in care doriti import-ul standard, fara module, acesta va arata in felul urmator: |
- | {{ :pw:laboratoare:screenshot_25.png?nolink |}} | + | |
- | Aceste **key**s (chei) ajuta React-ul la identificarea elementelor care s-au schimbat, adaugat si eliminat. Putem privi aceste chei precum identitatea unui element. | + | numeFisier.scss |
- | === 5.8 Compozitie === | + | <code> |
- | React are un model puternic de compozitie si va recomandam sa folositi compozitia in loc de mostenire pentru a reutiliza codul intre componente | + | .container { |
+ | display: flex; | ||
+ | justify-content: space-between; | ||
+ | align-items: center; | ||
+ | flex-direction: row; | ||
+ | padding: 1rem 0; | ||
+ | font-size: 1em; | ||
+ | font-weight: 700; | ||
+ | [...] | ||
+ | } | ||
- | Unele componente nu stiu din timp ce copii vor avea. Aceste componente sunt de obicei folosite cand ne definimi **Layout**-ul aplicatiei sau orice element care reprezinta o 'cutie' pentru elementele noastre. | + | import './numeFisier.scss'; |
- | Fiecare componenta React, are o proprietare rezervata in obiectul **props** numita **children**. Aceasta proprietate poate fi accesata in felul urmator: | + | <div className="container"> |
+ | [...] | ||
+ | </div> | ||
+ | </code> | ||
- | <code> | + | </note> |
- | Class components | + | |
- | this.props.children | + | 3. Modificati counter-ul astfel incat sa nu mai afiseze un numar, ci sa afiseze o imagine de **counter** ori. Stilizarea interfetei poate urma design-ul de mai jos, folosind culorile alese si fontul, dar tematica poate fi schimbata dupa propriile idei (Puteti sa faceti o tema inspirata dintr-un film sau natura). |
- | </code> | + | |
- | <code> | + | {{ :pw:laboratoare:screenshot_2021-04-25_at_22.34.34.png?nolink&700 |}} |
- | Functional components | + | |
- | props.children | ||
- | </code> | ||
- | {{ :pw:laboratoare:screenshot_26.png?nolink |}} | + | {{ :pw:laboratoare:screenshot_2021-04-25_at_22.29.40.png?nolink&700 |}} |
- | {{ :pw:laboratoare:screenshot_27.png?nolink |}} | + | |
- | ---- | + | 4. Componenta Layout va trebui sa alcatuiasca toata interfata utilizator, folosind **Header**, **props.children** si **Footer**. |
- | + | ||
- | ==== Exercitii ==== | + | |
- | - Generati o aplicatie React folosind **create-react-app**. | + | |
- | - Creati o componenta **Counter** care sa afiseze un numar si 3 butoane **Increment**, **Decrement** si **Reset**. | + | |
- | - Adaugati functionalitatea necesara pentru a incrementa, decrementa si reseta starea componentei **Counter**. | + | |
- | - Creati 4 componente (Class sau Functional) **Header**, **Nav**, **Footer** si **Layout**. | + | |
- | - Creati o sectiune in componenta **Layout** care sa reprezinte continutul afisat pe pagina, iar in iteriorul acestui continut sa putem adauga copii folosind compozitia. | + | |
- | - Adaugati **Header**, **Nav** si **Footer** in Layout pentru a defini structura paginii. | + | |
- | - Afisati componenta **Counter** in **Layout**ul pe care l-ati creat anterior. | + |