Додавання інтерактивності
На екрані деякі елементи оновлюються у відповідь на дії користувача. Наприклад, під час натискання на галерею зображень змінюється поточне зображення. У React дані, які поступово змінюються, називаються станом. Ви можете додавати стан до будь-якого компонента і оновлювати його за потреби. У цьому розділі ви дізнаєтеся, як писати компоненти, що оброблюють взаємодію, оновлюють свій стан та згодом відображають різний результат.
У цьому розділі
Реагування на події
React надає вам можливість додавати обробники подій до вашого JSX. Обробники подій — це ваші власні функції, які виконуватимуться у відповідь на різні взаємодії, як-от натискання мишкою, наведення курсора, фокусування в елементі введення даних у формі тощо.
Вбудовані компоненти, як-от <button>
, підтримують лише вбудовані браузерні події, наприклад, onClick
. Однак ви також можете створювати власні компоненти і надавати їхнім обробникам подій пропси з будь-якими назвами, слушними для вашого застосунку.
export default function App() { return ( <Toolbar onPlayMovie={() => alert('Відтворюється!')} onUploadImage={() => alert('Завантажується!')} /> ); } function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Відтворити фільм </Button> <Button onClick={onUploadImage}> Завантажити зображення </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }
Бажаєте вивчити цю тему?
Прочитайте розділ “Реагування на події”, щоб дізнатися, як додавати обробники подій.
ДетальнішеСтан: пам’ять компонента
Компоненти часто потребують змінювати те, що на екрані, унаслідок взаємодії. Введення у формі має оновлювати поле введення, натискання на кнопку “Далі” у каруселі зображень — змінювати відображуване зображення, а натискання на кнопку “Купити” — додавати продукт до кошика. Компонентам потрібно “пам’ятати” все це: поточне значення у полі введення, поточне зображення, продукти у кошику. У React цей вид пам’яті певного компонента називається стан.
Ви можете додати стан до компонента за допомогою хука useState
. Хуки — це спеціальні функції, які дають змогу вашим компонентам використовувати функції React (стан — одна з цих функцій). Хук useState
дає вам змогу оголосити змінну стану. Він приймає початковий стан і повертає пару значень: поточний стан і функцію встановлення стану, яка може його оновлювати.
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
Ось як галерея зображень використовує та оновлює стан після натискання:
import { useState } from 'react'; import { sculptureList } from './data.js'; export default function Gallery() { const [index, setIndex] = useState(0); const [showMore, setShowMore] = useState(false); const hasNext = index < sculptureList.length - 1; function handleNextClick() { if (hasNext) { setIndex(index + 1); } else { setIndex(0); } } 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} /> </> ); }
Бажаєте вивчити цю тему?
Прочитайте розділ “Стан: пам’ять компонента”, щоб дізнатися, як запам’ятати значення та оновлювати його внаслідок взаємодії.
ДетальнішеРендер і коміт
Перш ніж ваші компоненти відобразяться на екрані, їх повинен відрендерити React. Розуміння кроків цього процесу допоможе осмислити перебіг виконання вашого коду та пояснити його поведінку.
Уявіть, що ваші компоненти — це кухарі на кухні, які створюють смачні страви з інгредієнтів. У такій історії React — це офіціант, який приймає від клієнтів замовлення та видає їм їжу. Цей процес замовлення та видавання UI складається з трьох кроків:
- Тригер рендеру (доставлення замовлення гостя на кухню)
- Рендер компонента (готування замовлення на кухні)
- Коміт у DOM (розміщення замовлення на столі гостя)
Тригер Рендер Коміт
Ілюстровано Rachel Lee Nabors
Бажаєте вивчити цю тему?
Прочитайте розділ “Рендер і коміт” для вивчення життєвого циклу оновлення UI.
ДетальнішеСтан як снепшот
На відміну від звичайних змінних JavaScript, стан React поводиться радше як снепшот. Задання йому значення не змінює наявну змінну стану, а натомість запускає повторний рендер. Це може бути неочікувано на початку!
console.log(count); // 0
setCount(count + 1); // Запитує повторний рендер з 1
console.log(count); // Досі 0!
Ця поведінка допомагає уникнути дефектів. Ось невеличкий застосунок чату. Спробуйте вгадати, що відбудеться, якщо спершу натиснути “Надіслати”, а потім змінити отримувача на Боба. Чиє ім’я відображатиме alert
через п’ять секунд?
import { useState } from 'react'; export default function Form() { const [to, setTo] = useState('Аліса'); const [message, setMessage] = useState('Привіт'); function handleSubmit(e) { e.preventDefault(); setTimeout(() => { alert(`Ви надіслали ${message} користувачу ${to}`); }, 5000); } return ( <form onSubmit={handleSubmit}> <label> Кому:{' '} <select value={to} onChange={e => setTo(e.target.value)}> <option value="Alice">Аліса</option> <option value="Bob">Боб</option> </select> </label> <textarea placeholder="Повідомлення" value={message} onChange={e => setMessage(e.target.value)} /> <button type="submit">Надіслати</button> </form> ); }
Бажаєте вивчити цю тему?
Прочитайте розділ “Стан як снепшот”, щоб дізнатися, чому стан має “фіксований” і незмінний вигляд всередині обробників подій.
ДетальнішеДодавання до черги низки оновлень стану
Цей компонент має помилку: клацання “+3” збільшує лічильник лише на одиницю.
import { useState } from 'react'; export default function Counter() { const [score, setScore] = useState(0); function increment() { setScore(score + 1); } return ( <> <button onClick={() => increment()}>+1</button> <button onClick={() => { increment(); increment(); increment(); }}>+3</button> <h1>Загалом: {score}</h1> </> ) }
Розділ “Стан як снепшот” пояснює, чому таке відбувається. Задання значення стану запитує новий повторний рендеринг, але не змінює сам стан у коді, що вже виконується. У такий спосіб score
все ще дорівнює 0
одразу після виклику setScore(score + 1)
.
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
Ви можете виправити це, передаючи функцію-оновлювач під час задання стану. Зауважте, як заміна setScore(score + 1)
на setScore(s => s + 1)
виправляє кнопку “+3”. Це дає вам змогу додавати до черги кілька оновлень стану.
import { useState } from 'react'; export default function Counter() { const [score, setScore] = useState(0); function increment() { setScore(s => s + 1); } return ( <> <button onClick={() => increment()}>+1</button> <button onClick={() => { increment(); increment(); increment(); }}>+3</button> <h1>Загалом: {score}</h1> </> ) }
Бажаєте вивчити цю тему?
Прочитайте розділ “Додавання до черги низки оновлень стану”, щоб дізнатися, як додавати до черги послідовні оновлення стану.
ДетальнішеОновлення об’єктів у стані
Стан може зберігати будь-який тип значень JavaScript, включно з об’єктами. Але вам не слід безпосередньо змінювати об’єкти та масиви, які ви утримуєте в стані React. Замість цього, коли вам потрібно оновити об’єкт або масив, створіть новий (або зробіть копію наявного), а потім оновіть стан, щоб використовувати цю копію.
Зазвичай ви використовуватимете синтаксис поширення ...
для копіювання об’єктів та масивів, які ви хочете змінити. Наприклад, оновлення вкладеного об’єкта може мати такий вигляд:
import { useState } from 'react'; export default function Form() { const [person, setPerson] = useState({ name: 'Нікі де Сен Фаль (Niki de Saint Phalle)', artwork: { title: 'Синя "нана́" (Blue Nana)', city: 'Гамбург', image: 'https://i.imgur.com/Sd1AgUOm.jpg', } }); function handleNameChange(e) { setPerson({ ...person, name: e.target.value }); } function handleTitleChange(e) { setPerson({ ...person, artwork: { ...person.artwork, title: e.target.value } }); } function handleCityChange(e) { setPerson({ ...person, artwork: { ...person.artwork, city: e.target.value } }); } function handleImageChange(e) { setPerson({ ...person, artwork: { ...person.artwork, image: e.target.value } }); } return ( <> <label> Ім'я: <input value={person.name} onChange={handleNameChange} /> </label> <label> Назва: <input value={person.artwork.title} onChange={handleTitleChange} /> </label> <label> Місто: <input value={person.artwork.city} onChange={handleCityChange} /> </label> <label> Зображення: <input value={person.artwork.image} onChange={handleImageChange} /> </label> <p> <i>{person.artwork.title}</i> {' — '} {person.name} <br /> (місце розташування: {person.artwork.city}) </p> <img src={person.artwork.image} alt={person.artwork.title} /> </> ); }
Якщо копіювання об’єктів у коді виснажує, ви можете використовувати бібліотеку, як-от Immer, щоб зменшити повторюваний код:
{ "dependencies": { "immer": "1.7.3", "react": "latest", "react-dom": "latest", "react-scripts": "latest", "use-immer": "0.5.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" }, "devDependencies": {} }
Бажаєте вивчити цю тему?
Прочитайте розділ “Оновлення об’єктів у стані”, щоб дізнатися, як правильно оновлювати об’єкти.
ДетальнішеОновлення масивів у стані
Масиви — це ще один тип змінних об’єктів JavaScript, які ви можете зберігати у стані і повинні розглядати як доступні тільки для читання. Як і з об’єктами, коли вам потрібно оновити масив, що зберігається в стані, слід створити новий (або зробити копію наявного), а потім використати цей новий масив під час задання стану:
import { useState } from 'react'; const initialList = [ { id: 0, title: 'Великі животи (Big Bellies)', seen: false }, { id: 1, title: 'Місячний пейзаж (Lunar Landscape)', seen: false }, { id: 2, title: 'Теракотова армія (Terracotta Army)', seen: true }, ]; export default function BucketList() { const [list, setList] = useState( initialList ); function handleToggle(artworkId, nextSeen) { setList(list.map(artwork => { if (artwork.id === artworkId) { return { ...artwork, seen: nextSeen }; } else { return artwork; } })); } return ( <> <h1>Мистецький список</h1> <h2>Мій список для перегляду</h2> <ItemList artworks={list} onToggle={handleToggle} /> </> ); } function ItemList({ artworks, onToggle }) { return ( <ul> {artworks.map(artwork => ( <li key={artwork.id}> <label> <input type="checkbox" checked={artwork.seen} onChange={e => { onToggle( artwork.id, e.target.checked ); }} /> {artwork.title} </label> </li> ))} </ul> ); }
Якщо копіювання масивів у коді виснажує, ви можете використовувати бібліотеку, як-от Immer, щоб зменшити повторюваний код:
{ "dependencies": { "immer": "1.7.3", "react": "latest", "react-dom": "latest", "react-scripts": "latest", "use-immer": "0.5.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" }, "devDependencies": {} }
Бажаєте вивчити цю тему?
Прочитайте розділ “Оновлення масивів у стані”, щоб дізнатися, як правильно оновлювати масиви.
ДетальнішеЩо далі?
Перейдіть до розділу “Реагування на події”, щоб почати читати цю секцію посторінково!
Або, якщо ви вже знайомі з цими темами, чому б не переглянути “Управління станом”?