Урок по треку React 6 мин

Компонент на лайвкодинге: как не провалить

Типовые задачи live-coding: от счётчика до списка с поиском.

На лайвкодинге просят собрать небольшой компонент: поиск с подсказками, модалку, таймер, список задач, пагинацию. Проверяют не знание API, а то, как ты думаешь: состояние, крайние случаи, доступность, гонки.

План за две минуты

Прежде чем писать код, проговорите вслух:

  1. Что на входе и выходе: пропсы, колбэки, что видит пользователь.
  2. Минимальное состояние: только то, что нельзя вычислить. Отфильтрованный список не хранят в состоянии, его считают из исходного списка и запроса.
  3. Крайние случаи: пусто, загрузка, ошибка, очень длинный текст, быстрые повторные действия.
  4. Доступность: настоящие button, label у полей, управление с клавиатуры.

Интервьюер ценит этот план больше, чем скорость печати.

Производное состояние вместо синхронизации

// плохо: два источника правды и эффект-синхронизатор
const [filtered, setFiltered] = useState([]);
useEffect(() => setFiltered(items.filter((i) => i.done)), [items]);

// хорошо: считаем при рендере
const filtered = items.filter((i) => i.done);

Эффект здесь даёт лишний рендер и риск рассинхрона. Если вычисление дорогое, useMemo (или React Compiler).

Поиск с запросами: гонка ответов

Классика: пользователь быстро печатает «re», «rea», «react». Ответ на «re» может прийти последним и перетереть правильный результат. Решение: игнорировать устаревшие ответы или отменять запрос.

function Search() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  useEffect(() => {
    if (!query) { setResults([]); return; }
    const controller = new AbortController();
    const timer = setTimeout(() => {
      fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal: controller.signal })
        .then((r) => r.json())
        .then(setResults)
        .catch((e) => { if (e.name !== 'AbortError') console.error(e); });
    }, 300);
    return () => { clearTimeout(timer); controller.abort(); };
  }, [query]);

  return (
    <>
      <label htmlFor="q">Поиск</label>
      <input id="q" value={query} onChange={(e) => setQuery(e.target.value)} />
      <ul>{results.map((r) => <li key={r.id}>{r.title}</li>)}</ul>
    </>
  );
}

Очистка эффекта убирает таймер (это и есть debounce) и отменяет старый запрос. Не забудьте encodeURIComponent. В реальном проекте этот код заменяет TanStack Query, но на собесе просят написать руками.

Модалка

Что ожидают увидеть:

  • рендер через createPortal(content, document.body), чтобы overflow: hidden и z-index родителей не обрезали окно;
  • закрытие по Escape и по клику на подложку, но не по клику внутри окна (проверка e.target === e.currentTarget);
  • фокус переходит в окно при открытии и возвращается на кнопку при закрытии;
  • role="dialog", aria-modal="true", заголовок через aria-labelledby.

Нативный <dialog> с showModal() даёт ловушку фокуса, Escape и верхний слой без портала. Упомянуть его на собесе уместно.

Списки и key

Ключ должен быть стабильным и уникальным среди соседей: id из данных. Индекс массива как ключ ломает состояние элементов при удалении из середины или сортировке: React сопоставит состояние не тому элементу. Math.random() в ключе ещё хуже: каждый рендер пересоздаёт все элементы.

Таймеры и интервалы

useEffect(() => {
  if (!running) return;
  const id = setInterval(() => setSeconds((s) => s + 1), 1000);
  return () => clearInterval(id);
}, [running]);

Функциональное обновление s => s + 1 избавляет от устаревшего значения в замыкании, а очистка не даёт интервалам накапливаться.

Частые ошибки на лайвкодинге

  • Хранить в состоянии то, что вычисляется.
  • Забыть очистку эффекта: утечки таймеров и подписок.
  • Не обработать загрузку и ошибку.
  • div с onClick вместо кнопки.
  • Молча писать код 20 минут, не проговаривая решения.

Что спросят на собесе

  • Как избежать гонки ответов в поиске с подсказками?
  • Как сделать доступную модалку и зачем портал?
  • Почему не стоит использовать индекс массива как key?
  • Что хранить в состоянии, а что вычислять при рендере?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.

Вопросы модуля (45)