9 - Как работает и для чего нужен хук UseState?

Оценить качество материала и подачу материала автором видео:

Front-end

Трудоустройтесь middle front-end разработчиком на React JS (TypeScript) за 12-16 месяцев обучения с ежедневной менторской поддержкой в формате видео 1 на 1 и коммерческими проектами в портфолио

Перейти на курс
Front-end

Back-end

Трудоустройтесь middle back-end разработчиком за 12-16 месяцев обучения с ежедневной менторской поддержкой в формате видео 1 на 1 и коммерческими проектами в портфолио

Перейти на курс
Back-end

Карьерный бустер

Получите коммерческий опыт на реальных стартапах, прокачайте tech & soft навыки, научитесь работать в команде, проходить собеседования и получите первую работу в IT!

Перейти на курс
Карьерный бустер

Основы Front-end

Сделайте первый шаг в IT, освоив базовые знания разработки и научившись создавать небольшие проекты на JavaScript

Перейти на курс
Основы Front-end

Основы Back-end

Сделайте первый шаг в IT, освоив базовые знания разработки. Без опыта. Без математики. Только практика: JavaScript, SQL, Node JS, база данных

Перейти на курс
Основы Back-end

React – хук useState и Обработчики событий

Автор конспекта: Андрей Комаров

📚 1. Что такое реактивность в React?

Реактивность - это способность React автоматически перерисовывать пользовательский интерфейс при изменении данных. Когда состояние компонента меняется, React "реактивно" обновляет DOM, предоставляя новую разметку.

💡

Важно запомнить: React-компонент - это функция, которая возвращает JSX.

⚙️ 2. Обработчик событий onClick

  • Чтобы отловить событие клика на элементе (например, div), в React используется атрибут onClick.
  • Ему передается функция-колбэк, которая будет запущена, когда произойдет клик.
  • Эта функция действует как "слушатель события" (event listener), который реагирует на клик.

Декларативный подход: Вместо того чтобы вручную манипулировать стилями DOM, разработчик сосредоточен на изменении данных. Алгоритм отрисовки определяет, как разметка должна выглядеть в зависимости от состояния.

Повесим обработчик клика на div и передадим в него колбэк с id трека:

App.tsx
function App() {
  // остальной код
 
  return (
    <div>
      <h1>Musicfun</h1>
      <ul>
        {tracks.map((track) => (
          <li
            key={track.id}
            style={{ border: track.id === selectedTrackId ? "1px solid orange" : "none" }}
          >
            <div onClick={() => alert(track.id)}>{track.title}</div>
            {/*колбэк запустится в момент клика по div*/}
            <audio src={track.srс} controls={true} />
          </li>
        ))}
      </ul>
    </div>
  )
}

Кликаем по тайтлу первого трэка и видим 1 в сообщении screen_alert

Доступ к данным через замыкания:

  • В нашем примере, функция onClick для каждого трека формируется в контексте этого трека.
  • Благодаря замыканиям, каждая такая функция "помнит" track.id того конкретного трека, к которому она относится, даже после завершения отрисовки. Это позволит передавать правильный идентификатор в функцию-сеттер.

🚫 3. Проблема обычных переменных

Ключевая проблема: Как сделать так, чтобы интерфейс "реагировал" на действия пользователя? Например, менялся при клике? Обычные переменные в JavaScript для этого не подходят. Нужен инструмент, который скажет React: "Эй, данные поменялись, перерисуй всё заново!". Этот инструмент — хук useState.

Попробуем использовать обычную переменную для хранения состояния и просто переприсваивать её значение при клике:

App.tsx
import { track } from "mdast-util-to-markdown/lib/util/track"
function App() {
  let selectedTrackId = null // ❌ Не работает с React!
 
  if (tracks === null) {...}
  if (tracks.length === 0) {...}
 
   return (
    <div>
      <h1>Musicfun</h1>
      <ul>
        {tracks.map((track) => (
          <li key={track.id} style={{border: track.id === selectedTrackId ? "1px solid orange" : "none"}}>
            <div onClick={() => {
              selectedTrackId = track.id // ПРЯМОЕ ПРИСВАИВАНИЕ! React об этом не узнает.
              console.log("Клик! ID трека:", selectedTrackId) // В консоль будет выводиться, но UI не изменится.
            }}>{track.title}</div>
            <audio src={track.srс} controls={true}/>
          </li>
        ))}
      </ul>
    </div>
  )
}

Проверь в браузере. Открой консоль и кликай по трекам. Ты увидишь, что логи идут, но подсветки нет.

🤔 Почему это не работает?

⚠️
️️️Это критически важный момент для понимания.
  • selectedTrackId — это локальная переменная внутри функции App.

  • Когда React отрисовывает компонент, он вызывает функцию App, получает JSX и показывает его пользователю.

  • React не может отслеживать изменения обычных JavaScript-переменных, объявленных внутри компонента (например, let selectedTrackId = null).

  • Даже если такая переменная меняется, React не узнает об этом и не перерисует компонент.

  • Почему? У React нет доступа к этим переменным. Он может только вызывать функцию компонента, но не следить за её внутренними переменными.

🧠 4. Хук useState - "ячейка памяти" React

  • useState создает специальную ячейку памяти, за которой React может следить:
App.tsx
import { useState } from "react" // импортируем из react
 
function App() {
  // Создаем ячейку памяти с начальным значением null
  const stateManagement = useState(null) // вернёт массив из 2-х элементов
  stateManagement[0] = selectedTrackId // достаём текущее значение
  stateManagement[1] = setSelectedTrackId // достаём функцию для изменения значения
}

🔍 5. Как работает useState изнутри

Когда мы вызываем useState(null), происходит следующее:

  1. React создает fiber node - внутренний объект для хранения состояния компонента
  2. Сохраняет начальное значение в этот node
  3. Возвращает массив [текущее_значение, функция_обновления]
  4. При повторном рендере игнорирует начальное значение и возвращает актуальное

🧩 6. Деструктурирующее присваивание

useState возвращает массив, поэтому мы используем деструктуризацию:

App.tsx
// useState возвращает: [значение, функция_сеттер]
const stateArray = useState(null)
const selectedTrackId = stateArray[0]
const setSelectedTrackId = stateArray[1]
 
// Эквивалентно деструктуризации:
const [selectedTrackId, setSelectedTrackId] = useState(null)
🔗

Подробнее о деструктурирующем присваивании можно почитать по ссылке:

✅ 7. Визуальная обратная связь

Попытка №2. Передадим в колбэк функцию-сеттер, которую нам вернул useState:

App.tsx
function App() {
 
  const [selectedTrackId, setSelectedTrackId] = useState(null)
 
  if (tracks === null) {...}
  if (tracks.length === 0) {...}
 
  return (
    <div>
      <h1>Musicfun</h1>
      <button onClick={() => setSelectedTrackId(null)}>Reset selection</button>
      <ul>
        {tracks.map((track) => (
          <li key={track.id} style={{border: track.id === selectedTrackId ? '1px solid orange' : 'none'}}>
            <div onClick={() => {
              setSelectedTrackId(track.id) // передаём реакту актуальный id
            }}>{track.title}</div>
            <audio src={track.srс} controls={true}/>
          </li>
        ))}
      </ul>
    </div>
  )
}

🎉 Кликаем по тайтлам треков и видим что наш UI изменился и рамка отображается: screen_track_border

🔄 8. Кнопка сброса выбора

  • Чтобы сбросить выделение (например, по нажатию кнопки "Reset Selection"), достаточно вызвать setSelectedTrackId(null).

  • React сам уберет рамку, так как selectedTrackId снова станет null и не будет совпадать с track.id.

💡

Важно: Вы не думаете о прямом изменении DOM или удалении стилей; вы просто меняете данные, а React обновляет UI.

Добавим тэг button c атрибутом onClick и передадим в него null

App.tsx
function App() {
  const [selectedTrackId, setSelectedTrackId] = useState(null)
  // остальной код
 
  return (
    <div>
      <h1>Musicfun</h1>
      {/*обнуляем значение selectedTrackId при клике*/}
      <button onClick={() => setSelectedTrackId(null)}>Reset selection</button>{" "}
      <ul>
        {tracks.map((track) => ({
          /*мапаем трэки*/
        }))}
      </ul>
    </div>
  )
}
  • Кликаем по кнопке Reset selection и видим, что рамка исчезает

📊 9. Визуализация процесса:

scheme

  • 1: React вызывает компонент App для первой отрисовки JSX

  • 2: Компонент вызывает useState(null), прося React сохранить null в ячейке памяти

  • 3: React сохраняет null в fiber node, ассоциированном с компонентом

  • 4: React возвращает массив [null, setData] компоненту

  • 5: Компонент App возвращает JSX, который React визуализирует

  • 6: Пользователь кликает по элементу, вызывается setSelectedTrackId(track.id)

  • 7: Функция-сеттер обновляет значение в fiber node и просит React перерисовать компонент

  • 8: React снова вызывает компонент App

  • 9: useState возвращает актуальное значение из fiber node

  • 10: Компонент возвращает новый JSX с учетом обновленного состояния

🎓 Ключевые выводы

  • useState - хук для создания реактивного состояния. useState создает ячейку памяти, за которой следит React
  • Обычные переменные не вызывают перерисовку компонента
  • set-функция - единственный способ обновить состояние компонента и вызвать перерисовку
  • Замыкания позволяют обработчикам событий "помнить" свои значения
  • Декларативный подход - мы описываем как интерфейс должен выглядеть в зависимости от состояния, а React сам заботится об обновлениях
  • Обработчик события onClick - функция, которая запускается при клике
  • Условное отображение - показываем разную разметку в зависимости от state
  • Деструктурирующее присваивание - const [value, setValue] = useState(initial)
ℹ️

Примечание: TypeScript ошибки можно игнорировать на данном этапе, так как они не мешают работе приложения в браузере. TypeScript работает на этапе разработки, а не в рантайме.


🏠 Домашнее задание

Цель задания: Закрепить материал по useState и созданию интерактивных React-компонентов

Задание 1

Продолжай работать в проекте с предыдущего домашнего задания.

При клике на задачу покажи alert внутри которого выведи id задачи

💡 Подсказка:

  • Используй обработчик события onClick

Итоговый результат 🚀

les9-1

Задание 2

  • При клике на задачу она должна визуально отличаться. border сделай синего цвета

💡 Подсказка:

  • Храни id выбранной задачи(selectedTaskId) в state

Итоговый результат 🚀

les9-2

Задание 3

  • Добавь кнопку "Сбросить выделение", которая должна сбрасывать цвет border в первоначальное состояние

Итоговый результат 🚀

les9-3

Проверь себя

  • ✅ useState используется корректно
  • ✅ Деструктурирующее присваивание применено
  • ✅ onClick работает
  • ✅ Выделение задач функционирует
  • ✅ Кнопка сброса работает
  • ✅ React перерисовывает компонент при изменении state

💡
Удачи! 🚀 Пиши код и наблюдай, как React реактивно обновляет твой интерфейс!

Боевой маршрут (React Путь Самурая: без альтернатив)

Видеоурок - 10 видео из 30