Часта ў выніку ўзаемадзеяння кампаненты павінны змяніць тое, што адлюстроўваецца на экране. Увод у форму павінен абнавіць поле ўводу, націсканне кнопкі «далей» на каруселі відарысаў павінна змяніць відарыс, які адлюстроўваецца, націсканне кнопкі «купіць» павінна дадаць прадукт у кошык. Кампаненты павінны ўмець «запамінаць»: бягучае ўваходнае значэнне, бягучы відарыс, кошык для пакупак. У React такая памяць кампанента называецца стан.

You will learn

  • Як дадаць пераменную стану з дапамогай хука useState
  • Якую пару значэнняў вяртае хук useState
  • Як дадаць некалькі пераменных стану
  • Чаму стан называецца лакальным

Калі звычайнай пераменнай недастаткова

Вось кампанент, які адлюстроўвае відарыс скульптуры. Пры націсканні кнопкі «Далей» павінна паказацца наступная скульптура, змяніўшы значэнне index на 1, затым на 2 і гэтак далей. Аднак гэта не спрацуе (можаце паспрабаваць!):

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}>
        Далей
      </button>
      <h2>
        <i>«{sculpture.name}» </i> 
        {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} з {sculptureList.length})
      </h3>
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

Апрацоўшчык падзей handleClick абнаўляе лакальную пераменную index. Але два моманты не даюць пабачыць змены:

  1. Лакальныя пераменныя не захоўваюцца паміж рэндэрамі. Калі React рэндэрыць гэты кампанент у другі раз, ён рэндэрыць яго з нуля — не ўлічваючы ніякіх змен у лакальных пераменных.
  2. Змены ў лакальных пераменных не запускаюць паўторны рэндэр. React не разумее, што яму трэба зноў адрэндэрыць кампанент з новымі данымі.

Для таго, каб новыя даныя з’явіліся ў кампаненце, трэба зрабіць дзве рэчы:

  1. Захаваць даныя паміж рэндэрамі.
  2. Запусціць рэндэр кампанента з новымі данымі (перарэндэр).

Хук useState дае наступнае:

  1. Пераменную стану для захавання даных паміж рэндэрамі.
  2. Функцыю задання стану для абнаўлення пераменнай і запуску паўторнага рэндэру кампанента.

Дадаванне пераменнай стану

Каб дадаць пераменную стану, імпартуйце useState з React у пачатку файла:

import { useState } from 'react';

Затым замяніце гэты радок:

let index = 0;

на

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

index — гэта пераменная стану, а setIndex — функцыя задання стану.

Сінтаксіс [ і ] называецца дэструктурызацый масіву і дазваляе чытаць значэнні з масіву. Масіў, які вяртае useState, заўсёды мае два элементы.

Вось як яны працуюць разам у handleclick:

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

Цяпер націсканне кнопкі «Далей» пераключае бягучую скульптуру:

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}>
        Далей
      </button>
      <h2>
        <i>«{sculpture.name}» </i> 
        {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} з {sculptureList.length})
      </h3>
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

Пазнаёмцеся з вашым першым хукам

У React useState, як і любая іншая функцыя, якая пачынаецца з «use», называецца хукам.

Хукі — гэта спецыяльныя функцыі, даступныя толькі падчас рэндэру React (пра што мы падрабязней раскажам на наступнай старонцы). Яны дазваляюць вам падключацца («hook into») да розных функцый React.

Стан — гэта толькі адна з такіх функцый, але вы пазнаёміцеся з іншымі хукамі пазней.

Pitfall

Хукі (функцыі, якія пачынаюцца з use) можна выклікаць толькі на верхнім узроўні вашых кампанентаў або вашых уласных хукаў. Вы не можаце выклікаць хукі ўнутры ўмоў, цыклаў або іншых укладзеных функцый. Хукі — гэта функцыі, але карысна разглядаць іх як безумоўныя заявы аб патрэбах вашага кампанента. Вы «выкарыстоўваеце» (use) функцыі React у верхняй частцы вашага кампанента падобна таму, як вы «імпартуеце» (import) модулі ў верхняй частцы вашага файла.

Анатомія useState

Калі вы выклікаеце useState, вы кажаце React, што хочаце, каб гэты кампанент нешта запомніў:

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

У гэтым выпадку вы хочаце, каб React запомніў index.

Note

У адпаведнасці з пагадненнем, гэту пару прынята называць як const [something, setSomething]. Вы можаце называць гэту пару як хочаце, але пагадненні палягчаюць разуменне розных праектаў.

Адзіным аргументам для useState з’яўляецца пачатковае значэнне вашай пераменнай стану. У гэтым прыкладзе пачатковае значэнне index зададзена як 0 з дапамогай useState(0).

Кожны раз, калі ваш кампанент рэндэрыцца, useState дае вам масіў, які змяшчае два значэнні:

  1. Пераменную стану (index) са значэннем, якое вы захавалі.
  2. Функцыю задання стану (setIndex), якая можа абнаўляць пераменную стану і прымусіць React паўторна адрэндэрыць кампанент.

Вось як гэта адбываецца:

const [index, setIndex] = useState(0);
  1. Ваш кампанент рэндэрыцца ў першы раз. Паколькі вы перадалі 0 у функцыю useState як пачатковае значэнне для index, яна верне [0, setIndex]. React запамінае, што 0 — гэта апошняе значэнне стану.
  2. Вы абнаўляеце стан. Калі карыстальнік націскае кнопку, гэта выклікае setIndex(index + 1). Паколькі index роўны 0, таму насамрэч выклікаецца setIndex(1). Гэта загадвае React запомніць, што index цяпер роўны 1, і запускае яшчэ адзін рэндэр.
  3. Другі рэндэр вашага кампанента. React па-ранейшаму бачыць useState(0), але паколькі React памятае, што вы захавалі ў index значэнне 1, таму ён вяртае [1, setIndex].
  4. І гэтак далей!

Стварэнне ў кампаненце некалькіх пераменных стану

У адным кампаненце вы можаце мець любую колькасць пераменных стану розных тыпаў. Гэты кампанент мае дзве пераменныя стану, лік index і лагічнае значэнне 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}>
        Далей
      </button>
      <h2>
        <i>«{sculpture.name}» </i> 
        {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} з {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Паказаць' : 'Схаваць'} падрабязную інфармацыю
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
    </>
  );
}

Пажадана мець некалькі пераменных стану, калі іх станы не звязаныя паміж сабой, як, напрыклад, index і showMore у гэтым прыкладзе. Але калі вы часта змяняеце дзве пераменныя стану разам, магчыма аб’яднанне іх у адну пераменную дапаможа спрасціць ваш код. Напрыклад, калі ў вас ёсць форма з вялікай колькасцю палёў, зручней мець адну пераменную стану, якая змяшчае аб’ект, чым мець па адной пераменнай стану на кожнае поле. На старонцы «Выбар структуры стану» вы можаце знайсці больш дадатковых парад.

Deep Dive

Як React даведаецца, які стан вярнуць?

Магчыма, вы заўважылі, што выклік useState не атрымлівае ніякай інфармацыі аб тым, на якую пераменную стану ён спасылаецца. Няма «ідэнтыфікатара», які перадаецца ў useState, дык як ён ведае, якую з пераменных стану вярнуць? Ён выкарыстоўвае нейкую магію, накшталт разбору вашых функцый? Адказ — не.

Замест гэтага, каб забяспечыць іх лаканічны сінтаксіс, хукі разлічваюць на стабільны парадак выклікаў на кожным рэндэры аднаго і таго ж кампанента. Гэта добра працуе на практыцы, таму што калі вы будзеце прытрымлівацца правіла вышэй («выклікайце хукі толькі на верхнім узроўні»), хукі заўсёды будуць выклікацца ў адным і тым жа парадку. Акрамя таго, убудова для лінтара дазваляе выявіць большасць памылак.

Пад капотам React захоўвае масіў пар станаў для кожнага кампанента. Ён таксама падтрымлівае бягучы індэкс пары, якому перад рэндэрам задаецца значэнне 0. Кожны раз, калі вы выклікаеце useState, React дае вам наступную пару станаў і павялічвае індэкс. Больш даведацца аб гэтым механізме вы можаце ў артыкуле «React Hooks: Not Magic, Just Arrays» (Хукі ў React: не магія, а проста масівы).

У гэтым прыкладзе не выкарыстоўваецца React, але ён дае вам уяўленне аб тым, як useState працуе ўнутры:

let componentHooks = [];
let currentHookIndex = 0;

// Як працуе useState унутры React (спрошчана).
function useState(initialState) {
  let pair = componentHooks[currentHookIndex];
  if (pair) {
    // Гэта не першы рэндэр,
    // таму пара стану ўжо існуе.
    // Вернем яе і падрыхтуемся да наступнага выкліку хука.
    currentHookIndex++;
    return pair;
  }

  // Гэта першы рэндэр,
  // таму ствараем пару стану і захоўваем яе.
  pair = [initialState, setState];

  function setState(nextState) {
    // Калі карыстальнік запытвае змену стану,
    // захоўваем новае значэнне ў пары.
    pair[0] = nextState;
    updateDOM();
  }

  // Захаваем пару для будучых рэндэраў
  // і падрыхтуемся да наступнага выкліку хука.
  componentHooks[currentHookIndex] = pair;
  currentHookIndex++;
  return pair;
}

function Gallery() {
  // Кожны выклік useState() атрымае наступную пару.
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

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

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

  let sculpture = sculptureList[index];
  // У гэтым прыкладзе не выкарыстоўваецца React, таму
  // вяртаем выхадны аб'ект замест JSX.
  return {
    onNextClick: handleNextClick,
    onMoreClick: handleMoreClick,
    header: ${sculpture.name}» ${sculpture.artist}`,
    counter: `${index + 1} з ${sculptureList.length}`,
    more: `${showMore ? 'Паказаць' : 'Схаваць'} падрабязную інфармацыю`,
    description: showMore ? sculpture.description : null,
    imageSrc: sculpture.url,
    imageAlt: sculpture.alt
  };
}

function updateDOM() {
  // Скідваем бягучы індэкс хука
  // перад рэндэрам кампанента.
  currentHookIndex = 0;
  let output = Gallery();

  // Абнаўляем DOM, каб ён адпавядаў выхаду (пераменнай output).
  // Гэта тое, што React робіць за вас.
  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: 'Даніна нейрахірургіі',
  artist: 'Марта Колвін Андрадэ',
  description: 'Хаця Колвін пераважна вядомая сваімі абстрактнымі тэмамі, якія спасылаюцца на да-іспанскія сімвалы, гэтая гіганцкая скульптура, даніна павагі нейрахірургіі, з\'яўляецца адным з яе самых вядомых твораў мастацтва.',
  url: 'https://i.imgur.com/Mx7dA2Y.jpg',
  alt: 'Бронзавая статуя дзвюх скрыжаваных рук, якія далікатна трымаюць чалавечы мозг на кончыках пальцаў.'  
}, {
  name: 'Родавы Флораліс',
  artist: 'Эдуарда Каталана',
  description: 'Гэтая велізарная (75 футаў або 23 метры) серабрыстая кветка знаходзіцца ў Буэнас-Айрэсе. Яна спраектавана так, што можа закрываць пялёсткі ўвечары або пры моцным ветры і раскрываць іх раніцай.',
  url: 'https://i.imgur.com/ZF6s192m.jpg',
  alt: 'Гіганцкая металічная скульптура кветкі з люстэркавымі пялёсткамі і моцнымі тычынкамі.'
}, {
  name: 'Вечная прысутнасць',
  artist: 'Джон Вудраў Уілсан',
  description: 'Уілсан быў вядомы сваёй заклапочанасцю тэмамі роўнасці, сацыяльнай справядлівасці, а таксама істотнымі і духоўнымі якасцямі чалавецтва. Гэтая масіўная (7 футаў або 2,13 метра) бронзавая статуя ўяўляе тое, што ён назваў «сімвалічнай прысутнасцю чарнаскурых, прасякнутай пачуццём універсальнай чалавечнасці».',
  url: 'https://i.imgur.com/aTtVpES.jpg',
  alt: 'Скульптура з выявай чалавечай галавы здаецца ўсюдыіснай і панурай. Яна выпраменьвае спакой і ціхамірнасць.'
}, {
  name: 'Мааі',
  artist: 'Невядомы мастак',
  description: 'На востраве Пасхі знаходзіцца 1000 мааі, або захаваных манументальных статуй, створаных раннім народам Рапа-Нуі, якія, на думку некаторых, прадстаўлялі абагаўлёных продкаў.',
  url: 'https://i.imgur.com/RCwLEoQm.jpg',
  alt: 'Тры манументальныя каменныя бюсты з непрапарцыянальна вялікімі галовамі і змрочнымі тварамі.'
}, {
  name: 'Блакітная нана',
  artist: 'Нікі дэ Сен-Фаль',
  description: 'Наны — трыумфуючыя істоты, сімвалы жаноцкасці і мацярынства. Першапачаткова Сен-Фаль выкарыстаў тканіну і знайшоў прадметы для Нан, а пазней увёў поліэстэр для дасягнення больш яркага эфекту.',
  url: 'https://i.imgur.com/Sd1AgUOm.jpg',
  alt: 'Вялікая мазаічная скульптура мудрагелістай танцуючай жаночай фігуры ў маляўнічым касцюме, якая выпраменьвае радасць.'
}, {
  name: 'Канчатковая форма',
  artist: 'Барбара Хепуарт',
  description: 'Гэтая абстрактная бронзавая скульптура з\'яўляецца часткай серыі «Сям\'я чалавека», размешчанай у Ёркшырскім парку скульптур. Хепуарт вырашыла не ствараць літаральныя ўяўленні аб свеце, а распрацавала абстрактныя формы, натхнёныя людзьмі і ландшафтамі.',
  url: 'https://i.imgur.com/2heNQDcm.jpg',
  alt: 'Высокая скульптура з трох пастаўленых адзін на аднаго элементаў, якія нагадваюць фігуру чалавека.'
}, {
  name: 'Кавалер',
  artist: 'Ламідзі Аланадэ Фэйкі',
  description: "Праца Фэйкі, які з'яўляецца разьбяром па дрэве ў чацвёртым пакаленні, змяшала традыцыйныя і сучасныя тэмы ёруба.",
  url: 'https://i.imgur.com/wIdGuZwm.png',
  alt: 'Складаная драўляная скульптура воіна са сканцэнтраваным тварам на кані, упрыгожаная ўзорамі.'
}, {
  name: 'Вялікі пузы',
  artist: 'Аліна Шапачнікаў',
  description: "Шапачнікаў вядомая сваімі скульптурамі фрагментаванага цела як метафарай крохкасці і нясталасці маладосці і прыгажосці. Гэтая скульптура адлюстроўвае два вельмі рэалістычныя вялікія жываты, пакладзеныя адзін на аднаго, кожны вышынёй каля пяці футаў (1,5 м).",
  url: 'https://i.imgur.com/AlHTAdDm.jpg',
  alt: 'Скульптура нагадвае каскад зморшчын, зусім іншы, чым жываты ў класічных скульптурах.'
}, {
  name: 'Тэракотовая армія',
  artist: 'Невядомы мастак',
  description: 'Тэракотавая армія — гэта калекцыя тэракотавых скульптур, якія адлюстроўваюць армію Цынь Шыхуан-дзі, першага імператара Кітая. Войска налічвала больш за 8 тысяч воінаў, 130 калясніц з 520 коньмі і 150 кавалерыйскіх коней.',
  url: 'https://i.imgur.com/HMFmH6m.jpg',
  alt: '12 тэракотавых скульптур урачыстых воінаў, кожная з унікальным выразам твару і даспехамі.'
}, {
  name: 'Месяцовы пейзаж',
  artist: 'Луіза Нэвельсан',
  description: 'Нэвельсан была вядомая тым, што ачышчала аб\'екты з руін Нью-Ёрка, якія пазней яна збярэ ў манументальныя канструкцыі. У гэтай статуі яна выкарыстала разрозненыя часткі, такія як ложак, шпільку для жангліравання і фрагмент сядзення, прыбіўшы і склеіўшы іх у скрынкі, якія адлюстроўваюць уплыў геаметрычнай абстракцыі прасторы і формы кубізму.',
  url: 'https://i.imgur.com/rN7hY6om.jpg',
  alt: 'Чорная матавая скульптура, дзе асобныя элементы першапачаткова неадрозныя.'
}, {
  name: 'Арэол',
  artist: 'Ранджані Шэтар',
  description: 'Шэтар спалучае традыцыйнае і сучаснае, натуральнае і індустрыяльнае. Яе мастацтва засяроджана на ўзаемаадносінах чалавека і прыроды. Яе праца была апісана як пераканаўчая як у абстрактным, так і ў вобразным сэнсе, якая кідае выклік гравітацыі і ўяўляе сабой «вытанчаны сінтэз малаверагодных матэрыялаў».',
  url: 'https://i.imgur.com/okTpbHhm.jpg',
  alt: 'Бледная скульптура, падобная на дрот, усталяваная на бетоннай сцяне і спускаецца на падлогу. Здаецца вельмі лёгкай.'
}, {
  name: 'Бегемоты',
  artist: 'Тайбэйскі заапарк',
  description: 'Заапарк Тайбэя заказаў плошчу бегемотаў, на якой гулялі пагружаныя ў ваду бегемоты.',
  url: 'https://i.imgur.com/6o5Vuyu.jpg',
  alt: 'Група бронзавых скульптур бегемотаў, якія выходзяць з тратуара, нібы яны плывуць.'
}];

// Робім так, каб карыстальніцкі інтэрфейс адпавядаў першапачатковаму стану.
updateDOM();

Для таго, каб выкарыстоўваць React, вам не абавязкова трэба разумець гэта, але гэта можа быць карыснай ментальнай мадэллю.

Стан ізаляваны і прыватны

Стан з’яўляецца лакальным для асобніка кампанента на экране. Іншымі словамі, калі вы рэндэрыце адзін і той жа кампанент двойчы, кожная копія будзе мець цалкам ізаляваны стан! Змена аднаго з іх не паўплывае на іншы.

У гэтым прыкладзе кампанент Gallery, паказаны раней, рэндэрыцца двойчы без змяненняў у яго логіцы. Паспрабуйце панаціскаць на кнопкі ўнутры кожнай галерэі. Заўважце, што іх станы незалежныя:

import Gallery from './Gallery.js';

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

Гэта тое, што адрознівае стан ад звычайных пераменных, якія вы можаце аб’явіць у верхняй частцы вашага модуля. Стан не прывязаны да пэўнага выкліку функцыі або месца ў кодзе, ён «лакальны» для пэўнага месца на экране. Вы адрэндэрылі два кампаненты <Gallery />, таму іх станы захоўваюцца асобна.

Таксама звярніце ўвагу на тое, што кампанент Page нічога не «ведае» пра стан кампанента Gallery і нават пра тое, ці ёсць ён у яго наогул. У адрозненне ад пропсаў, стан з’яўляецца цалкам прыватным для кампанента, які яго аб’яўляе. Бацькоўскі кампанент не можа змяніць яго. Дзякуючы гэтаму, дадаванне стану да любога кампанента або яго выдаленне не ўплывае на астатнія кампаненты.

Што рабіць, калі вы хочаце, каб абедзве галерэі сінхранізавалі свае станы? Правільны спосаб зрабіць гэта ў React — гэта выдаліць стан з даччыных кампанентаў і дадаць яго да іх найбліжэйшага агульнага бацькоўскага кампанента. Наступныя некалькі старонак будуць прысвечаны арганізацыі стану аднаго кампанента, але мы вернемся да гэтай тэмы на старонцы «Сумеснае выкарыстанне стану кампанентамі.»

Recap

  • Выкарыстоўвайце пераменную стану калі кампаненту трэба «запомніць» нейкую інфармацыю паміж рэндэрамі.
  • Пераменныя стану аб’яўляюцца з дапамогай выкліку хука useState.
  • Хукі — гэта спецыяльныя функцыі, якія пачынаюцца з use. Яны дазваляюць падключацца («hook into») да функцый React, такіх як стан.
  • Хукі могуць нагадваць імпарт: яны павінны выклікацца безумоўна. Выклік хукаў, у тым ліку useState, дзейнічае толькі на верхнім узроўні кампанента або іншага хука.
  • Хук useState вяртае пару значэнняў: бягучы стан і функцыю для яго абнаўлення.
  • У вас можа быць больш за адну пераменную стану. Унутры React супастаўляе іх па парадку.
  • Стан з’яўляецца прыватным для кампанента. Калі вы рэндэрыце яго ў двух месцах, кожная копія атрымлівае свой уласны стан.

Калі вы націскаеце «Далей» на апошняй скульптуры, код выдае памылку. Выправіце логіку, каб прадухіліць памылку. Вы можаце зрабіць гэта, дадаўшы дадатковую логіку ў апрацоўшчык падзей або адключыўшы кнопку, калі дзеянне немагчыма.

Пасля ліквідацыі памылкі дадайце кнопку «Назад», якая паказвае папярэднюю скульптуру. Яна не павінна выдаваць памылку на першай скульптуры.

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}>
        Далей
      </button>
      <h2>
        <i>«{sculpture.name}» </i> 
        {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} з {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Паказаць' : 'Схаваць'} падрабязную інфармацыю
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
    </>
  );
}