Към съдържанието
wannacode

Състояние

Накратко: Състоянието (state) е стойност, която компонентът помни между рисуванията; в React се пази с useState, а всяка промяна рисува компонента наново.

Проблем

Бутонът не брои

Картата на продукта има бутон „Добави“ и брояч „В кошницата“. Натисни „Добави“ три пъти.

React

Конзолата казва count = 1, 2, 3. На картата пише 0. Грешка няма. Променливата count се сменя, а екранът не я следва. Къде е разликата?

Идеята

Обикновената променлива не стига

Картата не се рисува от твоя код, а от библиотеката. Тя рисува наново само когато знае, че нещо се е сменило. count++ сменя променливата, но не казва на никого.

ProductCard е функция. React я вика, когато рисува картата, и тя връща какво да се покаже. let count = 0 е локална променлива на това извикване. Кликът сменя нея, но никой не вика функцията наново, затова картата остава с 0.

Има и втори проблем. Ако React извика функцията наново по друга причина, първият ред е пак let count = 0. Всичко, натрупано в променливата, се губи.

Трябват данни, които компонентът помни между две рисувания и чиято промяна го рисува наново. Това се казва състояние (state).

Идеята

useState и ref

const [count, setCount] = useState(0);
function add() {
setCount(count + 1);
}
  • useState(0) връща две неща: стойността и функцията, която я сменя. Нулата е само началната стойност.
  • count е стойността за това рисуване.
  • setCount(1) запомня новата стойност и казва на React да нарисува картата наново. React вика ProductCard пак, а useState този път връща 1.

useState е hook: функция на React, която се вика в началото на компонента, никога в if или цикъл. React разпознава състоянията по реда, в който са извикани.

Обработчикът на клика се дава като функция: onClick={add}. Ако напишеш onClick={add()}, add се вика още докато картата се рисува. Тя сменя състоянието, което рисува пак, и React спира с грешка „Too many re-renders“.

React

Виж го

Виж какво става при клик

function ProductCard({ product }) {
const [count, setCount] = useState(0);
function addThree() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}
return (
<article>
<button onClick={addThree}>+3</button>
<p>В кошницата: {count}</p>
</article>
);
}
Стъпка по стъпка, с думи
  1. Картата пази count в състоянието на React: useState(0) дава стойността и функцията, която я сменя.
  2. React вика функцията ProductCard. Тя не пипа страницата, а връща описание: бутон и текст с count = 0.
  3. Първия път описанието става истински елементи в DOM-а. Страницата вече показва „В кошницата: 0“.
  4. Клик на +3: браузърът вика addThree.
  5. Три пъти setCount(count + 1). В това извикване count е снимка: 0. Всеки път на опашката отива „стани 1“. Екранът още не се сменя.
  6. Обработчикът свърши. React прилага опашката: 1, 1, 1 дава 1. После вика ProductCard наново и сега count е 1.
  7. React сравнява новото описание със старото. Бутонът е същият, сменен е само текстът. В DOM-а се сменя само той.
  8. Затова броячът показва 1, а не 3. Когато новата стойност зависи от старата, подай функция: setCount((c) => c + 1).

Картата не се строи наново. Библиотеката сравнява новото описание със старото и сменя в DOM-а само разликата: текста. Бутонът е същият елемент и фокусът остава на него. Това е разликата с render() от модула за DOM, която сменяше всичко.

Пробвай

Поправи брояча

Това е картата от началото. Направи count състояние, така че след всеки клик на „Добави“ картата да показва новия брой.

React

Идеята

Стойността е снимка

Минералната вода се продава и на стек от 6 бутилки. Удобно е бутонът за стек да вика add() шест пъти. Тук двете библиотеки се държат различно.

count е константа на едно рисуване. setCount не сменя count в обработчика, който тече в момента. То поставя заявка на опашка, а React я прилага след обработчика:

function addBox() {
for (let i = 0; i < 6; i++) {
setCount(count + 1); // 0 + 1, шест пъти: „стани 1“
}
}

Шестте заявки са „стани 1“ и броячът става 1, а не 6. Кажи „добави 1 към последната стойност“: подай функция вместо стойност.

setCount((c) => c + 1);

React вика всяка такава функция с резултата от предишната: 0 → 1 → 2 … → 6. Правилото: когато новата стойност зависи от старата, подавай функция.

Пробвай

Намери грешката

„Добави стек“ трябва да добави 6 бутилки. Кой ред е виновен?

Избери реда с грешката.

Пробвай

Стек вода

Направи addBox() така, че „Добави стек (6 бр.)“ да добавя 6 бутилки към това, което вече е в кошницата. Проверката натиска „Добави“ веднъж, после стека два пъти и очаква 1, 7 и 13.

React

Идеята

Всяка карта помни своето

Състоянието е на екземпляра, а не на кода. Каталогът има две карти от един и същ ProductCard, и всяка има свой count:

React

Натисни „Добави“ на хляба два пъти и на млякото веднъж: 2 и 1. Библиотеката пази състоянието на всяка карта до мястото ѝ в дървото на компонентите. Махнеш ли картата от страницата, състоянието ѝ изчезва с нея. Когато две карти трябва да показват едно и също число, то трябва да е в общия им родител. За това е урокът за споделеното състояние в този модул.

По-точно, за любопитните

Защо снимка. React не сменя count на място, защото функцията е извикана с тази стойност и всичко в нея, и обработчиците, вижда нея. Така едно рисуване е последователно: каквото е пресметнато от count, е пресметнато от една и съща стойност.

Кога React не рисува. Ако setCount получи същата стойност (сравнена с Object.is), React пропуска рисуването. Това е важно в следващия урок, за масивите.

Обновяванията се събират. Всички setCount в един обработчик дават едно рисуване, а от React 18 и в setTimeout и след await.

StrictMode. В разработка <StrictMode> вика всеки компонент два пъти, за да хване компонент, който сменя нещо отвън, докато се рисува. Пясъчникът не го ползва, а Vite проектът го има в main.jsx.

Класови компоненти. По-стар React код пише компонента като клас със this.state и this.setState. Новият код е с функции и hooks.

Упражнение

Избери броя

Каталогът има три карти. Във всяка добави брой, който започва от 1, бутон „−“ (име за екранния четец: „По-малко Бял хляб“) и бутон „+“ („Повече Бял хляб“) и ред „2 × 1,20 € = 2,40 €“ с броя, цената и сумата. „−“ е забранен (disabled), когато броят е 1. Всяка карта брои сама.

React

Тест

Провери се

1. Защо let count = 0 и count++ в компонента не сменят екрана?
2. Какво прави setCount(5)?
3. count е 0. Обработчикът вика setCount(count + 1) три пъти. Какво показва картата?
4. Защо onClick={add()} е грешка?
5. На страницата има две ProductCard, всяка със свой count. Купувачът натиска „Добави“ в първата. Какво става?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·