Додавання інтерактивності

На екрані деякі елементи оновлюються у відповідь на дії користувача. Наприклад, під час натискання на галерею зображень змінюється поточне зображення. У 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 складається з трьох кроків:

  1. Тригер рендеру (доставлення замовлення гостя на кухню)
  2. Рендер компонента (готування замовлення на кухні)
  3. Коміт у DOM (розміщення замовлення на столі гостя)
  1. React як офіціант у ресторані, що збирає замовлення від клієнтів і передає їх до кухні компонентів (Component Kitchen).
    Тригер
  2. Кухар карток видає React свіжий компонент картки (Card).
    Рендер
  3. React доставляє картку (Card) клієнту на стіл.
    Коміт

Ілюстровано 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": {}
}

Бажаєте вивчити цю тему?

Прочитайте розділ “Оновлення масивів у стані”, щоб дізнатися, як правильно оновлювати масиви.

Детальніше

Що далі?

Перейдіть до розділу “Реагування на події”, щоб почати читати цю секцію посторінково!

Або, якщо ви вже знайомі з цими темами, чому б не переглянути “Управління станом”?