Irbisa · Шпаргалка

Состояние и события

useState, состояние как снимок, батчинг, функция-апдейтер, иммутабельные обновления объектов и массивов, подъём состояния, события

Junior-разработчик · 20 вопросов

  1. 01

    Что такое состояние (state) в React и как работает useState?

    Состояние хранит данные, которые компонент помнит между рендерами и которые меняются со временем.

    type User = { name: string }; function Counter() {  const [count, setCount] = useState(0);  const [user, setUser] = useState<User | null>(null);…
  2. 02

    Почему изменение обычной локальной переменной не обновляет экран, а useState обновляет?

    Локальная переменная создаётся заново при каждом рендере, а её изменение ничего не сообщает React, поэтому ререндера нет, и следующий рендер снова начинается с начального значения.

  3. 03

    Как обрабатывать события в React и как передать аргумент в обработчик?

    В проп в camelCase, например onClick или onChange, передают функцию, и React вызывает её с объектом события, когда событие происходит.

    type Item = { id: string; title: string }; function TodoList({ items, onRemove }: { items: Item[]; onRemove: (id: string) => void }) {  function handleSubmit(e: React.FormEvent<HTMLFormElement>) {    e.preventDefault(); // stop the full page reload    console.log('submitted');…
  4. 04

    Найдите баг: этот компонент падает с ошибкой «Too many re-renders» при первом же рендере.

    function Greeting({ name }: { name: string }) {  const [upper, setUpper] = useState('');   setUpper(name.toUpperCase());   return <h1>{upper}</h1>;}

    Компонент вызывает setUpper во время рендера: каждый вызов планирует новый рендер, который снова его вызывает, и React обрывает цикл ошибкой «Too many re-renders.

  5. 05

    Почему стейт нужно обновлять иммутабельно, а не менять объект или массив на месте?

    React решает, нужен ли ререндер, сравнивая старое и новое состояние через Object.is, поэтому мутированный объект остаётся той же ссылкой, и обновление может быть пропущено.

  6. 06

    Как обновить объект в стейте, в том числе вложенный?

    Нужно создать новый объект через spread с изменённым полем и передать его в сеттер.

    type Person = { name: string; address: { city: string; zip: string } }; function Profile() {  const [person, setPerson] = useState<Person>({    name: 'Ada',    address: { city: 'London', zip: 'N1' },…
  7. 07

    Найдите баг: при вводе в поле поиска фильтр категории сбрасывается на вариант All.

    type Filters = { query?: string; category?: string }; function ProductFilters() {  const [filters, setFilters] = useState<Filters>({ category: 'books' });   return (    <>      <input value={filters.query ?? ''} onChange={(e) => setFilters({ query: e.target.value })} />      <select value={filters.category ?? ''} onChange={(e) => setFilters({ category: e.target.value })}>        <option value="">All</option>        <option value="books">Books</option>      </select>    </>  );}

    Сеттер useState заменяет стейт ровно тем, что вы передали, и ничего не сливает.

  8. 08

    Как добавлять, удалять и обновлять элементы массива в стейте?

    Используйте методы, которые возвращают новый массив: spread для добавления, filter для удаления, map для обновления, и передавайте результат в сеттер.

    type Todo = { id: number; text: string; done: boolean }; function useTodos(initial: Todo[]) {  const [todos, setTodos] = useState(initial);   const add = (text: string) =>…
  9. 09

    Что значит «поднять состояние» (lifting state up) и когда это делают?

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

    function Accordion() {  const [openIndex, setOpenIndex] = useState(0); // lifted from the panels   return (    <>      <Panel title="About" isOpen={openIndex === 0} onOpen={() => setOpenIndex(0)} />…
  10. 10

    Что значит «состояние является снимком» (state as a snapshot) в React?

    Каждый рендер видит фиксированное значение стейта на всё время своего существования: вызов сеттера не меняет уже имеющуюся у вас переменную, он просит React отрендерить компонент снова с новым значением.

  11. 11

    Что такое функция-апдейтер в сеттере стейта и когда её использовать?

    Вместо значения передают функцию, setCount((c) => c + 1), и React вызывает её с последним ожидающим значением стейта, когда обрабатывает очередь обновлений.

    function Counter() {  const [count, setCount] = useState(0);   function addThree() {    setCount((c) => c + 1);    setCount((c) => c + 1);…
  12. 12

    Что покажет alert и что покажет кнопка после одного клика и почему?

    function Counter() {  const [number, setNumber] = useState(0);   function handleClick() {    setNumber(number + 5);    setNumber((n) => n + 1);    setNumber(number + 1);    alert(number);  }   return <button onClick={handleClick}>{number}</button>;}

    Alert покажет 0, а кнопка затем покажет 1.

  13. 13

    Что такое батчинг в React и что изменил автоматический батчинг в React 18?

    Батчинг означает, что React объединяет несколько обновлений стейта в один ререндер, а не перерисовывает компонент после каждого вызова сеттера.

    import { flushSync } from 'react-dom'; function Chat() {  const [items, setItems] = useState<string[]>([]);  const [draft, setDraft] = useState('');  const listRef = useRef<HTMLUListElement>(null);…
  14. 14

    Найдите баг: после клика по «Add» новый тег не появляется, пока не начнёшь печатать в поле.

    function Tags() {  const [tags, setTags] = useState<string[]>(['react']);  const [text, setText] = useState('');   function handleAdd() {    tags.push(text);    setTags(tags);  }   return (    <>      <input value={text} onChange={(e) => setText(e.target.value)} />      <button onClick={handleAdd}>Add</button>      <p>{tags.join(', ')}</p>    </>  );}

    push мутирует существующий массив, а setTags(tags) передаёт ту же ссылку, поэтому проверка Object.is не видит изменений, и React пропускает ререндер.

  15. 15

    Что происходит, если вызвать сеттер стейта с тем же значением, которое уже лежит в стейте?

    React сравнивает новое и текущее значение через Object.is, и если они равны, пропускает ререндер компонента и его детей.

  16. 16

    Что такое ленивая инициализация стейта в useState и когда она нужна?

    Если передать функцию, useState(() => compute()), React вызовет её только в первом рендере.

    function Notes() {  // JSON.parse runs only on the first render  const [notes, setNotes] = useState<string[]>(() => {    const saved = localStorage.getItem('notes');    return saved ? (JSON.parse(saved) as string[]) : [];  });…
  17. 17

    Как правильно спроектировать стейт компонента: что в нём хранить, а что нет?

    Храните минимальный стейт, который нельзя вычислить: всё, что выводится из пропсов или другого стейта, вычисляется во время рендера, а не хранится.

  18. 18

    Как работают preventDefault, stopPropagation и обработчики фазы перехвата в React и куда React вешает слушатели событий?

    React оборачивает нативные события в SyntheticEvent, вешает по одному слушателю на каждый тип события на корневой контейнер и сам проводит событие по дереву компонентов.

    type CardProps = { onOpen: () => void; onShare: () => void }; function Card({ onOpen, onShare }: CardProps) {  return (    <div onClick={onOpen} onClickCapture={() => console.log('capture: runs first')}>      <a…
  19. 19

    Как обрабатывать частые события вроде scroll, resize или pointermove, не перерисовывая компонент на каждое из них?

    Не кладите каждое событие в стейт.

    function ArticleHeader() {  const barRef = useRef<HTMLDivElement>(null);  const [scrolled, setScrolled] = useState(false);   useEffect(() => {    let frame = 0;…
  20. 20

    Как реализовать undo и redo для части стейта в React?

    Храните историю вместо одного значения: past, present и future в одном useReducer.

    type History<T> = { past: T[]; present: T; future: T[] };type HistoryAction<T> = { type: 'set'; value: T } | { type: 'undo' } | { type: 'redo' }; const LIMIT = 100; // keep memory bounded function historyReducer<T>(state: History<T>, action: HistoryAction<T>): History<T> {…