Справочник по Angular и React · модуль 1 · первый проект и первый бот
1.4
Состояние
и события
В этой главе разобрано, как страница на React отвечает на действия пользователя. У карточки заявки появляется кнопка «Взять в работу», и по нажатию статус меняется на экране. Сначала показано, как подключить к кнопке функцию-обработчик и проверить её в консоли браузера. Затем — опыт с обычной переменной, которая меняется, а страница остаётся прежней, и хук useState, с которым новый статус появляется на экране. В конце состояние переносится из карточки в App, чтобы шапка доски показывала число новых заявок.
- Категория
- React
- Уровень
- Начальный
- Что понадобится
- Проект
zayavkiпосле главы 1.3, запущенныйnpm run dev, браузер Chrome, Edge или Яндекс Браузер - Результат
- Кнопка в карточке переводит заявку из «Новая» в «В работе» и «Готово», шапка показывает число новых заявок
§1Событие и обработчик
В лифте нажатие кнопки этажа — событие. Что после этого произойдёт, заложено заранее в схему лифта: закрыть двери и ехать на нужный этаж. Пассажир нажимает кнопку, действие выполняет механизм, подключённый к ней.
В лифте реакция на кнопку заложена при сборке. В React функцию, которая выполнится при нажатии, пишет разработчик.
Событие — то, что происходит на странице и о чём браузер может сообщить программе: щелчок мышью, нажатие клавиши, ввод текста в поле, отправка формы. Обработчик события — функция, которую браузер вызывает, когда событие произошло. В React обработчик подключают к тегу атрибутом, имя которого начинается с on: onClick — щелчок, onChange — изменение поля, onSubmit — отправка формы. Значение атрибута — сама функция в фигурных скобках: <button onClick={handleClick}>.
Функцию-обработчик обычно объявляют внутри компонента, перед return. По договорённости её имя начинается с handle («обработать») и продолжается названием события: handleClick, handleSubmit. Так по имени видно, что функция вызывается браузером в ответ на действие пользователя.
Кнопка в карточке
В первой версии обработчик печатает сообщение в консоль браузера, страница при этом не меняется. По сообщению в консоли видно, что щелчок дошёл до функции.
- Добавьте стили кнопки
Откройте
src/index.css, вставьте в самый конец файла правила ниже и сохраните.zayavki/src/index.css · в конец файла .card button { margin-top: 12px; padding: 6px 12px; border: 1px solid #2f6fde; border-radius: 6px; background: #ffffff; color: #2f6fde; font: inherit; font-size: 14px; cursor: pointer; } .card button:disabled { border-color: #c4cad8; color: #8a93a6; cursor: default; }Первое правило оформляет кнопку внутри карточки: синяя рамка, белый фон, отступ сверху. Второе, с
:disabled, задаёт серый вид выключенной кнопки; выключенная кнопка понадобится в §3. - Замените содержимое RequestCard.tsx
Откройте
src/components/RequestCard.tsx, замените весь текст кодом ниже и сохраните.zayavki/src/components/RequestCard.tsx type Status = 'Новая' | 'В работе' | 'Готово' type RequestCardProps = { title: string room: string details: string status?: Status } function RequestCard({ title, room, details, status = 'Новая' }: RequestCardProps) { function handleClick() { console.log('Нажата кнопка в заявке:', title) } return ( <article className="card"> <div className="card__top"> <span className="card__room">каб. {room}</span> <span className="card__status">{status}</span> </div> <h3>{title}</h3> <p>{details}</p> <button onClick={handleClick}>Взять в работу</button> </article> ) } export default RequestCard

Так должен выглядеть RequestCard.tsx: функция handleClick перед return, кнопка в конце карточки - Откройте консоль браузера
В браузере с доской заявок нажмите F12 (на macOS — Cmd+Option+J) и выберите вкладку Console. Нажмите кнопку «Взять в работу» в каждой из трёх карточек.

Так должен выглядеть результат: под каждой заявкой кнопка, в консоли три сообщения — по одному на нажатие
В функции handleClick одна команда — console.log. Она печатает в консоль браузера свои аргументы через пробел: строку 'Нажата кнопка в заявке:' и значение title. Консоль — панель инструментов разработчика, куда попадают сообщения программы и ошибки; на странице эти сообщения не видны. Справа от каждого сообщения указан файл и строка, где оно напечатано: RequestCard.tsx:12.
У каждой карточки свой обработчик с её собственным title: функция RequestCard вызывается три раза, и при каждом вызове создаётся отдельная handleClick. Поэтому в консоли три разных названия заявок. При открытии страницы в консоли может быть и сообщение React с предложением установить расширение React DevTools; для работы в этой главе оно не требуется.
В onClick={handleClick} после имени функции нет круглых скобок. Запись без скобок передаёт кнопке саму функцию, и браузер вызовет её при щелчке. Запись onClick={handleClick()} вызывает функцию сразу, при отрисовке карточки, и передаёт кнопке её результат. Что из этого получается, показано в §3.
Щелчок доходит до обработчика. Следующий шаг — изменение статуса заявки в обработчике.
§2Опыт с обычной переменной
Изменяемые данные в JavaScript хранят в переменной, объявленной словом let. Опыт проверяет, можно ли так хранить статус: переменная выводится в разметке и меняется в обработчике. Замените содержимое RequestCard.tsx кодом ниже и сохраните.
type Status = 'Новая' | 'В работе' | 'Готово' type RequestCardProps = { title: string room: string details: string status?: Status } function RequestCard({ title, room, details, status = 'Новая' }: RequestCardProps) { let current = status function handleClick() { current = 'В работе' console.log('current =', current) } return ( <article className="card"> <div className="card__top"> <span className="card__room">каб. {room}</span> <span className="card__status">{current}</span> </div> <h3>{title}</h3> <p>{details}</p> <button onClick={handleClick}>Взять в работу</button> </article> ) } export default RequestCard
Изменились три места. В строке 11 объявлена переменная current («текущий статус») с начальным значением из props. В обработчике ей присваивается 'В работе', и новое значение печатается в консоль. В разметке на месте статуса стоит {current}. Нажмите дважды «Взять в работу» в первой карточке.

В консоли сообщение current = В работе; кружок с цифрой 2 слева означает, что одинаковое сообщение напечатано два раза подряд. Значит, обработчик выполнился и переменная изменилась, но статус на карточке остался «Новая».
Причина в том, как React строит страницу. Разметку карточки возвращает функция RequestCard, и React вызывает её, когда отрисовывает компонент. Вызов функции компонента называется отрисовкой (в документации — render). После отрисовки React переносит полученную разметку в страницу и больше функцию не вызывает, пока не получит сигнал, что данные изменились. Присваивание current = 'В работе' такого сигнала не даёт: переменная поменялась, но функция RequestCard не вызвана заново, и разметка на странице осталась от первой отрисовки.
Если бы React и вызвал функцию заново, статус всё равно вернулся бы к «Новая»: строка let current = status выполняется при каждом вызове и создаёт переменную с начальным значением. Обычная переменная существует только внутри одного вызова функции.
Что показывает линтер
Эту ошибку находит линтер Oxlint, установленный в проект в главе 1.1. Откройте второй терминал (Terminal → New Terminal; первый занят сервером разработки) и выполните команду:
npm run lint

Оба предупреждения относятся к строке 14, символ 5 — к присваиванию current = 'В работе'. Первое переводится так: «нельзя присваивать переменной новое значение после завершения отрисовки; это может привести к несогласованному поведению при следующих отрисовках». В конце обоих сообщений подсказка: Consider using state instead — «используйте вместо этого состояние». Состояние разобрано в следующем разделе.
Нужна переменная, которая сохраняет значение между вызовами функции и при изменении вызывает отрисовку заново. В React это состояние.
§3Состояние: хук useState
В поликлинике над кабинетом висит табло с номером талона. Если врач запишет номер следующего пациента на листке у себя на столе, в коридоре номер не изменится. Чтобы номер поменялся для всех, врач нажимает кнопку пульта, и табло показывает новое значение. Обычная переменная из §2 похожа на листок на столе, функция изменения из useState — на кнопку пульта.
Табло меняет одно число. React после вызова функции изменения вызывает функцию компонента заново и переносит на страницу отличия новой разметки от прежней.
Состояние (state) — данные компонента, которые React хранит между отрисовками и при изменении которых отрисовывает компонент заново. Состояние создаётся функцией useState из библиотеки React. Функции React, имена которых начинаются с use, называются хуками (от англ. hook — крючок): через них компонент пользуется механизмами React, например хранением состояния.
const [current, setCurrent] = useState(status)
useState(status) получает начальное значение — здесь статус из props — и возвращает пару: текущее значение и функцию для его изменения. Квадратные скобки слева раскладывают пару по двум переменным, так же как фигурные скобки в главе 1.3 распаковывали объект props. Имена выбирает разработчик; по договорённости вторая переменная называется как первая с приставкой set: current и setCurrent. Слово const означает, что переменной нельзя присвоить другое значение: значение меняется только через setCurrent.
Вызов setCurrent('В работе') делает две вещи. Он сохраняет новое значение в React и сообщает React, что компонент нужно отрисовать заново. При следующем вызове функции RequestCard строка с useState вернёт уже 'В работе'; начальное значение используется только при первой отрисовке.
- Замените содержимое RequestCard.tsx
zayavki/src/components/RequestCard.tsx import { useState } from 'react' type Status = 'Новая' | 'В работе' | 'Готово' type RequestCardProps = { title: string room: string details: string status?: Status } function RequestCard({ title, room, details, status = 'Новая' }: RequestCardProps) { const [current, setCurrent] = useState(status) function handleClick() { if (current === 'Новая') { setCurrent('В работе') } else { setCurrent('Готово') } } return ( <article className="card"> <div className="card__top"> <span className="card__room">каб. {room}</span> <span className="card__status">{current}</span> </div> <h3>{title}</h3> <p>{details}</p> <button onClick={handleClick} disabled={current === 'Готово'}> {current === 'Новая' ? 'Взять в работу' : 'Завершить'} </button> </article> ) } export default RequestCard

Так должен выглядеть RequestCard.tsx: импорт useState в первой строке, состояние в строке 13 - Проверьте кнопки
Нажмите «Взять в работу» в первой карточке и «Взять в работу», затем «Завершить» во второй.

Так должна выглядеть страница: первая заявка «В работе», вторая и третья «Готово», их кнопки выключены
Первая строка import { useState } from 'react' подключает хук из библиотеки React. Фигурные скобки в импорте означают, что берётся функция с именем useState: библиотека React содержит много функций, и в файл подключают только нужные.
В обработчике стоит условие if … else. Знак === сравнивает два значения и даёт true, если они равны. Если текущий статус «Новая», он становится «В работе», иначе — «Готово». Так одна кнопка проводит заявку по двум шагам.
У кнопки появился второй атрибут: disabled={current === 'Готово'}. Атрибут disabled выключает кнопку, если его значение true: выключенная кнопка не нажимается, и правило .card button:disabled из первого шага красит её в серый цвет. Заявку в статусе «Готово» дальше переводить некуда, поэтому кнопка выключена.
Текст кнопки задан выражением {current === 'Новая' ? 'Взять в работу' : 'Завершить'}. Это тернарный оператор — условие в одну строку: условие ? значение_если_да : значение_если_нет. Пока заявка новая, на кнопке «Взять в работу», после этого — «Завершить». Условие с if внутри JSX записать нельзя, потому что в фигурных скобках JSX допускается только выражение, дающее значение; тернарный оператор — выражение.
Если сразу после setCurrent('В работе') в том же обработчике напечатать console.log(current), в консоли будет прежнее значение, «Новая». Вызов setCurrent не меняет переменную current в текущем вызове функции: новое значение она получит при следующей отрисовке. Поэтому в обработчике решение принимается по значению, которое было на экране в момент щелчка.
Вызов обработчика вместо передачи
В §1 говорилось, что после имени обработчика в onClick скобки не ставятся. Если в этой версии карточки написать onClick={handleClick()} и сохранить файл, страница станет пустой, а в консоли появится ошибка.

Ошибка Too many re-renders. React limits the number of renders to prevent an infinite loop переводится как «слишком много повторных отрисовок; React ограничивает их число, чтобы не допустить бесконечного цикла». Со скобками handleClick выполняется во время отрисовки и вызывает setCurrent. Вызов setCurrent требует новой отрисовки, в ней снова выполняется handleClick, и цикл не заканчивается. Строка ниже, An error occurred in the <RequestCard> component, указывает компонент, в котором произошла ошибка. Ту же ошибку до запуска показывает редактор: Type 'void' is not assignable to type 'MouseEventHandler<HTMLButtonElement> | undefined' — «результат функции нельзя передать туда, где ожидается обработчик щелчка». Уберите скобки и сохраните.
Кнопки работают. Следующий раздел показывает шаги между щелчком и новым статусом на экране.
§4Как React обновляет страницу
Между щелчком и новым статусом на экране проходит пять шагов. Схема ниже показывает их на первой карточке.
Шаги от щелчка до нового статуса на экране
Шаги 1–3 выполняет код обработчика. На шаге 4 React вызывает функцию компонента заново, и useState возвращает новое значение. На шаге 5 изменения попадают на страницу.
На шаге 5 React сравнивает разметку, которую функция вернула сейчас, с разметкой прошлой отрисовки и меняет в браузере только отличия — здесь текст статуса и текст кнопки. Остальная страница не перестраивается. Повторный вызов функции компонента React выполняет при каждом изменении состояния. Это та же формула из главы 0.1: интерфейс — функция от состояния, UI = f(state). Разработчик описывает, как выглядит карточка при данном статусе, а перенос изменений на страницу выполняет React.
У каждой карточки своё состояние
На снимке в §3 первая карточка перешла в «В работе», вторая — в «Готово», и друг на друга они не повлияли. Состояние привязано к каждому вызову RequestCard на странице. React хранит отдельную пару «значение и функция изменения» для каждой из трёх карточек и при отрисовке выдаёт каждой её собственное значение. Тот же компонент, вызванный в другом месте, начнёт со своего начального значения.
Каждая карточка хранит свой статус. Следующая задача — вывести в шапке число новых заявок, а для этого статусы нужны в одном месте.
§5Состояние в родителе
В ИТ-отделе три мастера, и каждый отмечает свои заявки в личном блокноте. Начальник отдела хочет знать, сколько заявок ещё никто не взял, и ему приходится обходить всех мастеров. Если журнал заявок лежит на столе у диспетчера, ответ виден сразу, а мастер, взяв заявку, звонит диспетчеру, и тот делает отметку в журнале.
Мастер звонит и сообщает, что взял заявку, — так карточка вызывает функцию, переданную ей от App.
Шапка доски должна показывать «Новых заявок: 1». Для этого числа нужны статусы всех трёх заявок, а сейчас каждый статус хранится внутри своей карточки, и ни App, ни Header к нему доступа не имеют: данные в React передаются только от родителя к ребёнку (глава 1.3, §6). Решение — хранить состояние в ближайшем общем родителе тех компонентов, которым оно нужно. Для карточек и шапки это App. Перенос состояния из дочерних компонентов в родителя называется подъёмом состояния (lifting state up).
После подъёма карточка снова получает статус через props, как в главе 1.3. Но менять его она не может: props только для чтения. Поэтому вместе со статусом родитель передаёт карточке функцию — prop onNext. Карточка вызывает её при щелчке, а функция в App меняет состояние. Prop с функцией, через который ребёнок сообщает родителю о событии, по договорённости называют с приставкой on: onNext, onDelete, onSave.
Передача данных и событий между App, шапкой и карточками
Статусы хранятся в App. Шапка получает готовое число, карточки — статус и функцию onNext. Щелчок в карточке вызывает onNext, App меняет состояние и отрисовывается заново вместе с шапкой и карточками.
- Добавьте стиль счётчика
В конец
src/index.cssдобавьте правило и сохраните.zayavki/src/index.css · в конец файла .header__count { display: inline-block; margin-top: 12px; padding: 3px 12px; border-radius: 12px; background: #2f6fde; font-size: 14px; } - Шапка получает число
Замените содержимое
src/components/Header.tsxи сохраните.zayavki/src/components/Header.tsx type HeaderProps = { newCount: number } function Header({ newCount }: HeaderProps) { return ( <header className="header"> <h1>Доска заявок</h1> <p>ИТ-отдел колледжа · заявки студентов и преподавателей</p> <span className="header__count">Новых заявок: {newCount}</span> </header> ) } export default Header
После сохранения VS Code подчеркнёт
<Header />вApp.tsx: шапке не передан обязательный propnewCount. Это исправит шаг 4. - Карточка получает статус и функцию
Замените содержимое
src/components/RequestCard.tsxи сохраните.zayavki/src/components/RequestCard.tsx export type Status = 'Новая' | 'В работе' | 'Готово' type RequestCardProps = { title: string room: string details: string status: Status onNext: () => void } function RequestCard({ title, room, details, status, onNext }: RequestCardProps) { return ( <article className="card"> <div className="card__top"> <span className="card__room">каб. {room}</span> <span className="card__status">{status}</span> </div> <h3>{title}</h3> <p>{details}</p> <button onClick={onNext} disabled={status === 'Готово'}> {status === 'Новая' ? 'Взять в работу' : 'Завершить'} </button> </article> ) } export default RequestCard
В
App.tsxподчеркнутся три вызова карточки: им не переданonNext. Их тоже исправит шаг 4. - Состояние в App
Замените содержимое
src/App.tsxи сохраните.zayavki/src/App.tsx import { useState } from 'react' import Header from './components/Header' import RequestForm from './components/RequestForm' import RequestCard, { type Status } from './components/RequestCard' function nextStatus(status: Status): Status { if (status === 'Новая') { return 'В работе' } return 'Готово' } function App() { const [status1, setStatus1] = useState<Status>('Новая') const [status2, setStatus2] = useState<Status>('В работе') const [status3, setStatus3] = useState<Status>('Готово') const newCount = [status1, status2, status3].filter((s) => s === 'Новая').length return ( <> <Header newCount={newCount} /> <main className="board"> <RequestForm /> <section className="list"> <h2>Заявки</h2> <RequestCard title="Не работает проектор" room="214" details="Проектор включается, но изображения на экране нет." status={status1} onNext={() => setStatus1(nextStatus(status1))} /> <RequestCard title="Нет интернета" room="305" details="На всех компьютерах в кабинете не открываются сайты." status={status2} onNext={() => setStatus2(nextStatus(status2))} /> <RequestCard title="Замятие бумаги в принтере" room="112" details="Принтер пишет «замятие бумаги», лоток пустой." status={status3} onNext={() => setStatus3(nextStatus(status3))} /> </section> </main> </> ) } export default App

Так должен выглядеть App.tsx: функция nextStatus, три состояния, подсчёт newCount; счётчик ошибок внизу — 0 - Проверьте счётчик
Обновите страницу в браузере. В шапке — «Новых заявок: 1». Нажмите «Взять в работу» в первой карточке.

До щелчка: одна новая заявка 
После щелчка: первая заявка «В работе», в шапке «Новых заявок: 0»
Разбор RequestCard.tsx
Из карточки удалены useState и handleClick: своего состояния у неё больше нет. В типе props два изменения. status: Status снова обязательный, без ? и без значения по умолчанию, потому что статус теперь всегда приходит из App. Новое поле onNext: () => void описывает функцию: пустые скобки — функция без параметров, void после стрелки — функция ничего не возвращает. Кнопка получает эту функцию напрямую: onClick={onNext}. Перед type Status появилось слово export: тип нужен в App.tsx, и его нужно сделать доступным из других файлов.
Разбор App.tsx
Четвёртая строка импортирует из одного файла два имени: RequestCard — экспорт по умолчанию, без скобок, и Status — именованный экспорт, в фигурных скобках. Слово type перед Status отмечает, что подключается только тип. Описания типов удаляются при переводе TypeScript в JavaScript, и настройки проекта Vite требуют отмечать такие импорты явно; без слова type редактор покажет ошибку TS1484.
Функция nextStatus записана вне App: состояние и props в ней не используются, она вычисляет следующий статус по текущему. Запись (status: Status): Status означает «принимает статус и возвращает статус». Логика та же, что была в handleClick карточки: из «Новая» — «В работе», из остальных — «Готово».
Три строки с useState создают три состояния — по одному на заявку. В угловых скобках useState<Status> указан тип значения. Без них TypeScript вывел бы из начального значения 'Новая' тип string, то есть любую строку, и передать такое значение в prop status типа Status было бы нельзя. В карточке в §3 угловые скобки не требовались: начальным значением был prop, у которого тип Status уже указан.
Строка с newCount считает новые заявки. [status1, status2, status3] собирает три статуса в массив — список значений в квадратных скобках. Метод filter оставляет в массиве только элементы, для которых условие s === 'Новая' истинно, а .length даёт их количество. Работа с массивами подробно разобрана в главе 1.5. Число newCount не хранится в состоянии: оно вычисляется из состояния при каждой отрисовке App. Данные, которые можно вычислить из уже имеющихся, в отдельное состояние не записывают, иначе два значения пришлось бы обновлять вместе и они могли бы разойтись.
Каждой карточке передаются status={status1} и onNext={() => setStatus1(nextStatus(status1))}. Запись () => … — стрелочная функция: короткая функция без имени, у которой после стрелки записано, что она делает. Здесь она нужна, потому что onNext должен получить функцию, которую карточка вызовет позже. Запись без стрелки, onNext={setStatus1(nextStatus(status1))}, вызвала бы setStatus1 сразу при отрисовке — та же ошибка, что с handleClick() в §3.
Цепочка при щелчке в первой карточке такая. Кнопка вызывает onNext, то есть стрелочную функцию из App. Та вычисляет nextStatus('Новая'), получает «В работе» и вызывает setStatus1. React отрисовывает App заново: status1 равен «В работе», newCount пересчитывается и становится 0, шапка и карточки получают новые props.
Начальное значение useState(status) используется только при первой отрисовке. Если бы карточка хранила статус у себя, а App потом передал бы ей другой status, карточка продолжала бы показывать своё значение. У одного статуса было бы два места хранения, и они могли бы разойтись — та же ошибка, что в витрине с ручным обновлением из главы 0.1. Поэтому каждое значение хранится в одном компоненте, а остальные получают его через props.
В App.tsx остался повтор: три состояния и три почти одинаковых вызова карточки. Для доски с десятками заявок статусы хранят одним списком и выводят карточки из него циклом. Это тема главы 1.5.
§6Частые ошибки
Сообщения ниже получены в проекте zayavki: ошибки типов — командой tsc -b, предупреждения — командой npm run lint, ошибка «Too many re-renders» — в консоли браузера.
| Что видно | Почему | Что сделать |
|---|---|---|
Пустая страница, в консоли Too many re-renders. React limits the number of renders to prevent an infinite loop. | Обработчик вызван при отрисовке: onClick={handleClick()} | Передать функцию без скобок: onClick={handleClick} или onClick={() => …} |
Type 'void' is not assignable to type 'MouseEventHandler<HTMLButtonElement> | undefined' | Та же ошибка, найденная редактором до запуска | Убрать скобки после имени обработчика |
Type 'void' is not assignable to type '() => void' | В prop-функцию передан результат вызова: onNext={setStatus1(…)} | Обернуть вызов в стрелочную функцию: onNext={() => setStatus1(…)} |
Cannot find name 'useState' | Нет импорта хука | Добавить первой строкой import { useState } from 'react' |
Линтер: react(immutability): Cannot reassign variable after render completes | Обработчик присваивает новое значение обычной переменной или prop | Хранить значение в состоянии и менять через функцию из useState |
Линтер: react-hooks(rules-of-hooks): React Hook "useState" is called conditionally | useState вызван внутри if. React различает состояния компонента по порядку вызовов хуков, и этот порядок должен быть одинаковым при каждой отрисовке | Вызывать хуки в начале функции компонента, вне условий и циклов |
'Status' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled | Тип импортирован без слова type | Записать import RequestCard, { type Status } from … |
Property 'onNext' is missing in type '{ … }' but required in type 'RequestCardProps' | Карточке не передана функция onNext | Добавить атрибут onNext={() => …} |
| Кнопка нажимается, в консоли сообщение, на странице ничего не меняется | Значение хранится в обычной переменной | Перенести значение в состояние (§3) |
§7Проверьте себя
Что такое обработчик события и как подключить его к кнопке?
Функция, которую браузер вызывает, когда произошло событие. К кнопке её подключают атрибутом onClick, передавая саму функцию без скобок: <button onClick={handleClick}>.
Почему присваивание обычной переменной в обработчике не меняет страницу?
Страница меняется, когда React вызывает функцию компонента заново, а присваивание переменной такого вызова не вызывает. Кроме того, обычная переменная создаётся заново при каждом вызове функции, и при следующей отрисовке значение вернулось бы к начальному.
Что возвращает useState('Новая') и что делает функция изменения?
Пару: текущее значение и функцию для его изменения. Функция изменения сохраняет новое значение в React и запускает повторную отрисовку компонента; при ней useState вернёт новое значение.
Чем отличаются onClick={handleClick} и onClick={handleClick()}?
Первая запись передаёт кнопке функцию, и браузер вызовет её при щелчке. Вторая вызывает функцию сразу при отрисовке и передаёт кнопке результат. Если функция меняет состояние, вторая запись даёт бесконечный цикл отрисовок и ошибку «Too many re-renders».
Шапке нужно число новых заявок, а статусы хранятся в карточках. Что сделать?
Поднять состояние в ближайшего общего родителя — App. App хранит статусы, считает число новых и передаёт его шапке, а карточкам — статус и функцию onNext, которую карточка вызывает при щелчке.
Почему newCount не хранится в отдельном состоянии?
Его можно вычислить из статусов, которые уже хранятся в состоянии. Вычисление выполняется при каждой отрисовке App, и число всегда соответствует статусам. Отдельное состояние пришлось бы обновлять вместе со статусами, и значения могли бы разойтись.
Добавьте в карточку кнопку «Вернуть», которая переводит заявку обратно в «Новая». Что нужно изменить?
Четыре изменения:
- в тип props карточки добавить
onReset: () => voidи принятьonResetв параметре; - добавить в карточку
<button onClick={onReset}>Вернуть</button>; - в
Appпередать каждой карточке функцию:onReset={() => setStatus1('Новая')}, для второй —setStatus2, для третьей —setStatus3; - проверить, что счётчик в шапке увеличивается после нажатия.
Шпаргалка
<button onClick={handleClick}>const [value, setValue] = useState(0)import { useState } from 'react'useState<Status>('Новая')onNext: () => voidonNext={() => setStatus1('Готово')}{ok ? 'Да' : 'Нет'}disabled={status === 'Готово'}Итог главы
Событие — действие пользователя, о котором сообщает браузер; обработчик — функция, подключённая к тегу атрибутом onClick, onChange или другим с приставкой on. Функция передаётся без скобок, иначе она выполнится при отрисовке. Изменение обычной переменной не меняет страницу: React обновляет разметку, только когда вызывает функцию компонента заново. Повторную отрисовку запускает изменение состояния — значения из хука useState, которое React хранит между отрисовками; меняется оно функцией вида setValue. После изменения React вызывает компонент, сравнивает новую разметку с прежней и меняет на странице только отличия. Если состояние нужно нескольким компонентам, его хранят в их общем родителе: родитель передаёт детям данные через props, а дети сообщают о событиях, вызывая переданные функции. Доска заявок переводит заявки по статусам кнопкой в карточке и показывает в шапке число новых.
Источники
- react.dev — Responding to Events
- react.dev — State: A Component's Memory
- react.dev — Render and Commit
- react.dev — State as a Snapshot
- react.dev — Sharing State Between Components
- react.dev — Rules of Hooks
- developer.chrome.com — Console overview