React State: useState Hook

React State: useState Hook

Komponentlər çox vaxt istifadəçi ilə qarşılıqlı əlaqə nəticəsində ekranda olanı dəyişməlidir. Form-a yazmaq input sahəsini yeniləməlidir, “next” düyməsinə klikləmək şəkil karuselində göstərilən şəkli dəyişməlidir, “buy” düyməsinə klikləmək məhsulu səbətə əlavə etməlidir. Komponentlər bəzi şeyləri “xatırlamalıdır”: cari input dəyəri, cari şəkil, alış-veriş səbəti. React-də bu cür komponentə xas yaddaş state adlanır.

React State: Komponentin Yaddaşı bloqunda öyrənəcəklərimiz:

  • - useState Hook-u ilə state dəyişəni necə əlavə etmək olar
  • - useState Hook-un hansı iki dəyəri geri qaytardığını
  • - Necə bir neçə state dəyişəni əlavə etmək olar
  • - State-in niyə lokal adlandırıldığını

Budur bir React komponent, hansı ki, bir heykəlin şəklini göstərir. “Next” düyməsinə klikləmək növbəti heykəli göstərməlidir – bunun üçün index əvvəlcə 1, sonra 2 və s. olmalıdır. Amma bu kod işləməyəcək (cəhd edə bilərsiniz!):

import { sculptureList } from './data.js';

export default function Gallery() {
 let index = 0;

 function handleClick() {
   index = index + 1;
 }

 let sculpture = sculptureList[index];
 return (
   <>
     <button onClick={handleClick}>
       Next
     </button>
     <h2>
       <i>{sculpture.name} </i> 
       by {sculpture.artist}
     </h2>
     <h3>  
       ({index + 1} of {sculptureList.length})
     </h3>
     <img 
       src={sculpture.url} 
       alt={sculpture.alt}
     />
     <p>
       {sculpture.description}
     </p>
   </>
 );
}

export const sculptureList = [{
 name: 'Homenaje a la Neurocirugía',
 artist: 'Marta Colvin Andrade',
 description: 'Colvin əsasən pre-hispanik simvollara işarə edən abstrakt mövzuları ilə tanınsa da, bu nəhəng heykəl, neyrocərrahiyyəyə bir hörmət olaraq, onun ən tanınan ictimai sənət nümunələrindən biridir.',
 url: 'https://i.imgur.com/Mx7dA2Y.jpg',
 alt: 'Barmaqları ilə insan beynini incə şəkildə tutan iki çarpaz əlin bürünc heykəli.'  
}, {
 name: 'Floralis Genérica',
 artist: 'Eduardo Catalano',
 description: 'Bu nəhəng (75 fut və ya 23 m) gümüşü çiçək Buenos Ayresdə yerləşir. O, hərəkət etmək üçün hazırlanıb: axşamlar və ya güclü külək zamanı ləçəklərini bağlayır, səhərlər isə açır.',
 url: 'https://i.imgur.com/ZF6s192m.jpg',
 alt: 'Əks etdirici, güzgü kimi ləçəkləri və güclü erkəkcikləri olan nəhəng metal çiçək heykəli.'
}, {
 name: 'Eternal Presence',
 artist: 'John Woodrow Wilson',
 description: 'Wilson bərabərlik, sosial ədalət və insanlığın mənəvi keyfiyyətləri ilə maraqlanması ilə tanınırdı. Bu nəhəng (7 fut və ya 2,13 m) bürünc heykəl onun “universal insanlıq hissi ilə dolu simvolik Qara varlıq” kimi təsvir etdiyi şeyi təmsil edir.',
 url: 'https://i.imgur.com/aTtVpES.jpg',
 alt: 'İnsan başını təsvir edən heykəl daim mövcud və ciddi görünür. O, sakitlik və rahatlıq saçır.'
}]

handleClick hadisəsini işləyən funksiya lokal index dəyişənini yeniləyir. Amma iki səbəbdən bu dəyişiklik görünmür:

  • 1. Lokal dəyişənlər renderlər arasında yadda qalmır. React bu komponenti ikinci dəfə render etdikdə, onu sıfırdan render edir — lokal dəyişənlərdəki dəyişiklikləri nəzərə almır.
  • 2. Lokal dəyişənlərdəki dəyişikliklər komponentin yenidən render olunmasına səbəb olmur. React yeni məlumatlarla komponenti yenidən göstərmək lazım olduğunu başa düşmür.

Bir komponenti yeni məlumatlarla yeniləmək üçün iki hadisə baş verməlidir:

  • 1. Məlumat renderlər arasında yadda saxlanmalıdır.
  • 2. React yeni məlumatlarla komponenti yenidən göstərməlidir (re-render).

useState Hook-u bu iki şeyi təmin edir:

  • 1. Renderlər arasında məlumatı saxlayan state dəyişəni
  • 2. Dəyişəni yeniləyən və React-i komponenti yenidən göstərməyə məcbur edən funksional setter

State dəyişəni əlavə etmək

State dəyişəni əlavə etmək üçün, faylın yuxarısında React-dən useState-i import edin:

import { useState } from 'react';

Sonra bu sətri dəyişdirin:

let index = 0;

bu kod ilə əvəz edin:

const [index, setIndex] = useState(0);

index bir state dəyişənidir və setIndex isə onu dəyişmək üçün istifadə olunan funksiyadır.

Buradakı [ ] sintaksisi array destructuring adlanır və sizə array dəyərləri oxumağa imkan verir. useState tərəfindən qaytarılan array həmişə məhz iki elementdən ibarət olur.

Bu dəyərlər handleClick funksiyasında bu şəkildə birlikdə işləyirlər:

function handleClick() {
 setIndex(index + 1);
}

İndi “Next” düyməsinə klikləmə hazırda göstərilən heykəli dəyişir:

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
 const [index, setIndex] = useState(0);

 function handleClick() {
   setIndex(index + 1);
 }

 let sculpture = sculptureList[index];
 return (
   <>
     <button onClick={handleClick}>
       Next
     </button>
     <h2>
       <i>{sculpture.name} </i> 
       by {sculpture.artist}
     </h2>
     <h3>  
       ({index + 1} of {sculptureList.length})
     </h3>
     <img 
       src={sculpture.url} 
       alt={sculpture.alt}
     />
     <p>
       {sculpture.description}
     </p>
   </>
 );
}

İlk React Hook-unla tanış ol

React-də useState, həmçinin “use” ilə başlayan hər hansı digər funksiya Hook adlanır.

Hook-lar yalnız React render edərkən (bu barədə növbəti bloqda daha ətraflı danışılacaq) istifadə oluna bilən xüsusi funksiyalardır. Onlar sənə React-in müxtəlif imkanlarına “qoşulmağa” imkan verir.

State bu imkanlardan sadəcə biridir, lakin digər Hook-larla da sonradan tanış olacaqsan.

Note:

“use” ilə başlayan Hook-lar (məsələn, useState, useEffect) yalnız komponentin ən yuxarı səviyyəsində çağırıla bilər. Yəni, bu Hook-ları if, for, function kimi blokların içində çağırmaq olmaz. React Hook-ların çağırılma sırasını yadda saxlayır. Əgər sən Hook-u şərtin içində çağırsan, o zaman React-in çağırılma sırası pozulur və bu səhvlərə səbəb olur. Hook-ları adi funksiyalar kimi yox, şərtsiz ifadə kimi düşünmək lazımdır. Yəni, onlar sanki bir kitabxananı import etmək kimi bir şeydir — komponentin yuxarısında və hər zaman eyni yerdə olmalıdır.

Yanlış:

if (isLoggedIn) {
 const [user, setUser] = useState(null); //  olmaz
}

Doğru:

const [user, setUser] = useState(null); //  yuxarıda, şərtsiz

React-da useState-in anatomiyası

useState çağırdığın zaman, React-ə bu komponentin nəyisə yadda saxlamasını istədiyini bildirirsən:

const [index, setIndex] = useState(0);

Bu halda, sən React-in index-i yadda saxlamasını istəyirsən.

Qeyd

Qəbul olunmuş adlandırma forması bu cütlüyü belə adlandırmaqdır:
const [something, setSomething].
İstəsən, onu istədiyin adla adlandıra bilərsən, amma layihələr arasında anlaşmanı asanlaşdırmaq üçün konvensiyalara əməl etmək daha yaxşıdır.

useState funksiyasına verilən yeganə arqument state dəyişənin ilkin qiymətidir. Bu nümunədə, index-in ilkin qiyməti useState(0) ilə 0 olaraq təyin edilib.

Hər dəfə komponentin render edildiyi zaman useState sənə iki dəyərdən ibarət bir array qaytarır:

  • State dəyişəni (index), yəni yadda saxlanan dəyər;
  • State setter funksiyası (setIndex), yəni həmin dəyişəni yeniləyib React-in komponenti yenidən render etməsini təmin edən funksiya.

Bax, bu proses necə işləyir:

const [index, setIndex] = useState(0);

  • Komponent ilk dəfə render olunur.
    useState-ə index üçün ilkin dəyər kimi 0 verdiyin üçün, o [0, setIndex] qaytarır. React 0-ı ən son dəyər kimi yadda saxlayır.
  • State yenilənir.
    İstifadəçi düyməni klikləyir, bu da setIndex(index + 1) funksiyasını çağırır. index hal-hazırda 0-dır, ona görə də setIndex(1) çağırılır. Bu, React-ə index-in artıq 1 olduğunu bildirir və yeni renderi başladır.
  • Komponentin ikinci dəfə renderi.
    React hələ də useState(0) görür, lakin yadda saxladığına görə index-in artıq 1 olduğunu bilir və bu dəfə [1, setIndex] qaytarır.

Və bu proses davam edir!

React komponentinə bir neçə state dəyişəni əlavə etmək

Bir komponent daxilində istədiyin qədər və istədiyin tipdə state dəyişəni ola bilər. Aşağıdakı komponentdə iki state dəyişəni var: biri number tipində olan index, digəri isə “Show details” düyməsini kliklədikdə dəyişən boolean tipli showMore:

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
 const [index, setIndex] = useState(0);
 const [showMore, setShowMore] = useState(false);

 function handleNextClick() {
   setIndex(index + 1);
 }

 function handleMoreClick() {
   setShowMore(!showMore);
 }

 let sculpture = sculptureList[index];
 return (
   <>
     <button onClick={handleNextClick}>
       Next
     </button>
     <h2>
       <i>{sculpture.name} </i> 
       by {sculpture.artist}
     </h2>
     <h3>  
       ({index + 1} of {sculptureList.length})
     </h3>
     <button onClick={handleMoreClick}>
       {showMore ? 'Hide' : 'Show'} details
     </button>
     {showMore && <p>{sculpture.description}</p>}
     <img 
       src={sculpture.url} 
       alt={sculpture.alt}
     />
   </>
 );
}

Əgər state-lər bir-biri ilə əlaqəli deyilsə (məsələn bu nümunədəki index və showMore kimi), bir neçə state dəyişənindən istifadə etmək yaxşı fikirdir.
Amma əgər iki state dəyişənini tez-tez birlikdə dəyişirsənsə, onları bir yerdə saxlamaq daha asan ola bilər. Məsələn, əgər bir neçə sahədən ibarət bir forman varsa, hər sahə üçün ayrı-ayrı state dəyişəni saxlamaq əvəzinə, hamısını bir obyekt daxilində saxlamaq daha əlverişli ola bilər. Daha ətraflı məsləhətlər üçün “Choosing the State Structure” bölməsinə bax.

React hansı state-i qaytardığını necə bilir?

Bəlkə də fərq etmisən: useState çağırışında həmin state dəyişəninə aid hər hansı bir məlumat verilməyib. useState-ə heç bir “identifikator” ötürülmür, bəs o necə başa düşür ki, hansı state dəyişənini qaytarmalıdır? Səncə o, funksiyanı analiz edib bir sehrlə müəyyənləşdirir?
Cavab: Xeyr.

Əvəzində, bu yığcam sintaksisi mümkün etmək üçün, Hooks-lar hər komponentin hər renderində sabit çağırılma sırasına güvənir. Bu, praktikada çox yaxşı işləyir, çünki yuxarıdakı qaydaya əməl etsən (“Hooks-ları yalnız komponentin ən yuxarı səviyyəsində çağır”), onlar həmişə eyni sırada çağırılır.
Əlavə olaraq, linter plugin əksər səhvləri avtomatik müəyyən edir.

Daxildə, React hər bir komponent üçün state cütlüklərindən ibarət bir array saxlayır. Həmçinin, cari cütlük üçün bir indeks saxlayır və bu indeks hər renderdən əvvəl 0 olaraq təyin olunur. Hər dəfə useState çağırdıqda, React növbəti state cütlüyünü qaytarır və indeks artırılır.
Bu mexanizm barədə daha ətraflı “React Hooks: Not Magic, Just Arrays” məqaləsində oxuya bilərsən.

Bu nümunə React istifadə etmir, lakin bu, useState-in daxildə necə işlədiyi barədə sizə fikir verir:

let componentHooks = [];
let currentHookIndex = 0;

// React daxilində useState-in necə işlədiyi (sadələşdirilmiş).
function useState(initialState) {
 let pair = componentHooks[currentHookIndex];
 if (pair) {
   // Bu ilk render deyil,
   // ona görə də state cütlüyü artıq mövcuddur.
   // Onu qaytar və növbəti Hook çağırışına hazırlaş.
   currentHookIndex++;
   return pair;
 }

 // Bu ilk dəfə render edirik,
 // ona görə də bir state cütlüyü yarat və onu yadda saxla.
 pair = [initialState, setState];

 function setState(nextState) {
   // İstifadəçi state dəyişməsini istədikdə,
   // yeni dəyəri cütlüyə yaz.
   pair[0] = nextState;
   updateDOM();
 }

 // Gələcək renderlər üçün cütlüyü yadda saxla
 // və növbəti Hook çağırışına hazırlaş.
 componentHooks[currentHookIndex] = pair;
 currentHookIndex++;
 return pair;
}

function Gallery() {
 // Hər useState() çağırışı növbəti cütlüyü alacaq.
 const [index, setIndex] = useState(0);
 const [showMore, setShowMore] = useState(false);

 function handleNextClick() {
   setIndex(index + 1);
 }

 function handleMoreClick() {
   setShowMore(!showMore);
 }

 let sculpture = sculptureList[index];
 // Bu nümunə React istifadə etmir, ona görə də
 // JSX əvəzinə çıxış obyektini qaytarır.
 return {
   onNextClick: handleNextClick,
   onMoreClick: handleMoreClick,
   header: `${sculpture.name} by ${sculpture.artist}`,
   counter: `${index + 1} of ${sculptureList.length}`,
   more: `${showMore ? 'Hide' : 'Show'} details`,
   description: showMore ? sculpture.description : null,
   imageSrc: sculpture.url,
   imageAlt: sculpture.alt
 };
}

function updateDOM() {
 // Komponenti render etməzdən əvvəl cari Hook indeksini sıfırla.
 currentHookIndex = 0;
 let output = Gallery();

 // DOM-u çıxışa uyğun yenilə.
 // Bunu React sizin yerinizə edir.
 nextButton.onclick = output.onNextClick;
 header.textContent = output.header;
 moreButton.onclick = output.onMoreClick;
 moreButton.textContent = output.more;
 image.src = output.imageSrc;
 image.alt = output.imageAlt;
 if (output.description !== null) {
   description.textContent = output.description;
   description.style.display = '';
 } else {
   description.style.display = 'none';
 }
}

let nextButton = document.getElementById('nextButton');
let header = document.getElementById('header');
let moreButton = document.getElementById('moreButton');
let description = document.getElementById('description');
let image = document.getElementById('image');
let sculptureList = [{
 name: 'Homenaje a la Neurocirugía',
 artist: 'Marta Colvin Andrade',
 description: 'Colvin əsasən pre-Hispanik simvollara işarə edən abstrakt mövzularla tanınsa da, bu nəhəng heykəl — neyrocərrahiyəyə hörmət — onun ən tanınan ictimai sənət əsərlərindən biridir.',
 url: 'https://i.imgur.com/Mx7dA2Y.jpg',
 alt: 'İki çarpaz əl insan beynini barmaqlarının ucunda incəliklə tutmuş bürünc heykəl.'
}, {
 name: 'Floralis Genérica',
 artist: 'Eduardo Catalano',
 description: 'Bu nəhəng (75 fut və ya 23 m) gümüşü çiçək Buenos Airesdə yerləşir. O, hərəkət etmək üçün hazırlanıb — axşamlar və ya güclü küləklərdə ləçəklərini bağlayır, səhərlər isə açır.',
 url: 'https://i.imgur.com/ZF6s192m.jpg',
 alt: 'Güzgü effekti yaradan metal ləçəkləri və güclü erkəkcikləri olan nəhəng metal çiçək heykəli.'
}, {
 name: 'Eternal Presence',
 artist: 'John Woodrow Wilson',
 description: 'Wilson bərabərlik, sosial ədalət və insanlığın əsas və mənəvi keyfiyyətləri ilə bağlı narahatlığı ilə tanınırdı. Bu nəhəng (7 fut və ya 2,13 m) bürünc heykəl onun "ümumbəşəri insanlıq hissi ilə aşılanmış simvolik Qara varlıq" kimi təsvir etdiyi şeyi təmsil edir.',
 url: 'https://i.imgur.com/aTtVpES.jpg',
 alt: 'İnsan başını təsvir edən heykəl daimi və təntənəli görünür. O, sakitlik və dinclik saçır.'
}];

// UI-nı ilkin state ilə uyğunlaşdır.
updateDOM();

HTML kodu:

<button id="nextButton">
 Next
</button>
<h3 id="header"></h3>
<button id="moreButton"></button>
<p id="description"></p>
<img id="image">

<style>
* { box-sizing: border-box; }
body { font-family: sans-serif; margin: 20px; padding: 0; }
button { display: block; margin-bottom: 10px; }
</style>

State is isolated and private

State, ekrandakı komponent instansiyasına lokal şəkildə aiddir. Başqa sözlə, əgər eyni komponenti iki dəfə render etsəniz, hər bir nüsxə tamamilə ayrı state-ə sahib olacaq! Onlardan birində dəyişiklik etmək digərinə təsir etməyəcək.

Bu nümunədə, əvvəlki Gallery komponenti heç bir məntiq dəyişikliyi olmadan iki dəfə render olunur. Qalereyalardan hər birinin içindəki düymələri klikləməyi yoxlayın. Diqqət edin ki, onların state-i bir-birindən asılı deyil:

import Gallery from './Gallery.js';

export default function Page() {
 return (
   <div className="Page">
     <Gallery />
     <Gallery />
   </div>
 );
}

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
 const [index, setIndex] = useState(0);
 const [showMore, setShowMore] = useState(false);

 function handleNextClick() {
   setIndex(index + 1);
 }

 function handleMoreClick() {
   setShowMore(!showMore);
 }

 let sculpture = sculptureList[index];
 return (
   <section>
     <button onClick={handleNextClick}>
       Next
     </button>
     <h2>
       <i>{sculpture.name} </i> 
       by {sculpture.artist}
     </h2>
     <h3>  
       ({index + 1} of {sculptureList.length})
     </h3>
     <button onClick={handleMoreClick}>
       {showMore ? 'Hide' : 'Show'} details
     </button>
     {showMore && <p>{sculpture.description}</p>}
     <img 
       src={sculpture.url} 
       alt={sculpture.alt}
     />
   </section>
 );
}

Bu, state-in moduldakı dəyişənlərdən fərqli olmasının səbəbidir. State konkret bir funksiya çağırışına və ya koddakı bir yerə bağlı deyil, lakin ekrandakı müəyyən bir hissəyə "lokal" şəkildə aiddir. Siz iki ədəd <Gallery /> komponenti render etdiniz, buna görə onların state-i ayrıca saxlanılır.

Həmçinin diqqət yetirin ki, Page komponenti Gallery-nin state-i haqqında heç nə “bilmir”, hətta onun olub-olmadığını da. React.js Props Idarəetməsi bloqunda öyrəndiyimiz kimi Props-dan fərqli olaraq, state onu təyin edən komponent üçün tamamilə özəldir. Parent komponent onu dəyişə bilməz. Bu, sizə istənilən komponentə state əlavə etməyə və ya onu silməyə imkan verir – digər komponentlərə təsir etmədən.

Bəs əgər istəsəydiniz ki, hər iki qalereya state-lərini sinxron saxlasın? React-də bunu etməyin düzgün yolu – state-i child komponentlərdən çıxarıb, onların ortaq yaxın parentinə əlavə etməkdir. Növbəti səhifələr tək bir komponentin state-inin təşkilinə fokuslanacaq, lakin biz bu mövzuya Komponentlər Arasında State Paylaşımı bölməsində qayıdacağıq.

Nəticə

Bu bloq yazısında React komponentlərinin öz daxili yaddaşını necə idarə etdiyini və useState Hook-u ilə bunu necə tətbiq edə biləcəyimizi öyrəndik. Ənənəvi lokal dəyişənlər renderlər arasında məlumatı saxlaya bilmədiyi üçün, useState React komponentinin yaddaşlı davranışını təmin edir və istifadəçinin qarşılıqlı əlaqəsinə əsasən ekranın yenilənməsini mümkün edir. Bundan əlavə, Hook-ların yalnız komponentin yuxarı səviyyəsində və şərtsiz çağırılmalı olduğunu xatırlamaq vacibdir. Bu qaydalara əməl etməklə React ilə hazırlanan vebsaytları daha stabil və təmiz şəkildə yaza bilərsiniz.

Frontend Müsahibə Sualları

1. useState və lokal dəyişənlər arasında əsas fərq nədir?

2. React komponentində bir neçə useState necə istifadə olunur və bu performansa necə təsir edir?

3. Niyə useState yalnız komponentin yuxarı səviyyəsində çağırılmalıdır və bu qaydanın pozulması hansı problemlərə yol aça bilər?