Задачи с кодом

Код-ревью на собеседовании: как найти проблемы в React-компоненте

Обновлено 6 мин чтения
Содержание
  1. Коротко
  2. Как проходит и что оценивают
  3. Компонент для разбора
  4. Восемь проблем и что о них сказать
  5. В каком порядке отвечать
  6. Как формулировать замечания
  7. Исправленная версия
  8. Частые ошибки
  9. Чек-лист
  10. Вопросы из базы
  11. Как тренировать
  12. Частые вопросы
Коротко

Код-ревью на собеседовании это задание «вот компонент, найдите в нём проблемы»: вы читаете чужой код вслух и объясняете, что сломается и как починить. Так проверяют то, что нужно в работе каждый день: умение читать код, видеть баги и спокойно говорить о них. Ниже разобран типичный React-компонент с восемью проблемами, порядок ответа и исправленный код.

Как проходит и что оценивают

Вам присылают ссылку на редактор или показывают экран с кодом на 30-80 строк. Обычно это React-компонент, кастомный хук или функция работы с данными. На ревью дают 15-30 минут. Иногда просят сразу исправить код, иногда достаточно назвать проблемы.

Что смотрятКак это видно
Находите ли настоящие багиГонки, утечки, неверное состояние, а не только стиль
Понимаете ли последствияГоворите, что увидит пользователь, если оставить как есть
Умеете ли расставить приоритетыСначала то, что ломает продукт, в конце мелочи
Как общаетесьЗамечания по делу, без оценок автора
Ревью отличается от лайвкодинга: здесь не нужно придумывать решение с нуля. Зато нужно быстро понять чужой замысел и не утонуть в мелочах.

Компонент для разбора

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

type Comment = { id: number; author: string; html: string };

export function Comments({ postId }: { postId: number }) {
  const [comments, setComments] = useState<Comment[]>([]);
  const [query, setQuery] = useState('');
  const [filtered, setFiltered] = useState<Comment[]>([]);

  useEffect(() => {
    fetch(`/api/posts/${postId}/comments`)
      .then((r) => r.json())
      .then((data) => setComments(data));
  }, []);

  useEffect(() => {
    setFiltered(comments.filter((c) => c.author.includes(query)));
  }, [comments, query]);

  useEffect(() => {
    const id = setInterval(() => {
      fetch(`/api/posts/${postId}/comments`)
        .then((r) => r.json())
        .then((data) => setComments(data));
    }, 10000);
  }, [postId]);

  function addLocal(author: string) {
    comments.push({ id: Date.now(), author, html: '' });
    setComments(comments);
  }

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <button onClick={() => addLocal('Я')}>Добавить</button>
      {filtered.map((c, i) => (
        <div key={i}>
          <b>{c.author}</b>
          <div dangerouslySetInnerHTML={{ __html: c.html }} />
        </div>
      ))}
    </div>
  );
}

Восемь проблем и что о них сказать

1. Неполные зависимости первого эффекта

Что проверяют
Понимание того, когда перезапускается эффект.
Как отвечать
В массиве зависимостей пусто, а внутри используется postId. Если компонент получит другой postId без перемонтирования, на экране останутся комментарии старой статьи. Нужно добавить postId в зависимости. Линтер правил хуков подсветил бы это сразу.
Тезисы ответа и тренировка вслух

2. Гонка запросов

Что проверяют
Асинхронность в эффектах.
Как отвечать
Если postId быстро сменился, ответ на старый запрос может прийти позже нового и перезаписать данные. В очистке эффекта нужно отменять запрос через AbortController или игнорировать устаревший ответ.
Тезисы ответа и тренировка вслух

3. Интервал без очистки

Что проверяют
Утечки и жизненный цикл компонента.
Как отвечать
setInterval никогда не останавливается. После ухода со страницы запросы продолжатся, а при смене postId интервалы будут копиться. Эффект должен вернуть функцию, которая вызывает clearInterval.
Тезисы ответа и тренировка вслух

4. Мутация состояния

Что проверяют
Неизменяемость state в React.
Как отвечать
push меняет тот же массив, а setComments получает ту же ссылку. React сравнивает по ссылке, видит, что ничего не изменилось, и может не перерисовать компонент. Нужно создать новый массив: setComments((prev) => [...prev, newComment]).
Тезисы ответа и тренировка вслух

5. Индекс в качестве key

Что проверяют
Как React сопоставляет элементы списка.
Как отвечать
При фильтрации и добавлении элементов индексы сдвигаются, и React переиспользует не те DOM-узлы и состояние. У комментария есть стабильный id, его и нужно взять в key.
Тезисы ответа и тренировка вслух

6. XSS через dangerouslySetInnerHTML

Что проверяют
Безопасность фронтенда.
Как отвечать
HTML комментария пишут пользователи. Вставка без очистки позволяет выполнить чужой скрипт в браузере каждого читателя. Варианты: показывать текст, а не HTML, или очищать разметку проверенной библиотекой-санитайзером перед вставкой.
Тезисы ответа и тренировка вслух

7. Лишнее состояние для производных данных

Что проверяют
Проектирование state.
Как отвечать
filtered полностью вычисляется из comments и query. Отдельный state с эффектом даёт лишний рендер и риск рассинхронизации. Достаточно посчитать список прямо при рендере, а при тяжёлом расчёте обернуть в useMemo.
Тезисы ответа и тренировка вслух

8. Нет загрузки и ошибок

Что проверяют
Внимание к пользователю.
Как отвечать
Пока данные грузятся, пользователь видит пустой экран, а при ошибке сети или ответе 500 ничего не узнает. Нужно проверять r.ok, хранить статус загрузки и ошибку и показывать их. Копия кода запроса в двух эффектах тоже повод вынести загрузку в одну функцию.

В каком порядке отвечать

Не перечисляйте замечания в порядке строк. Сначала скажите, что делает компонент, одной фразой. Так интервьюер поймёт, что вы прочитали замысел. Дальше идите от важного к мелкому.

  1. Баги, которые увидит пользователь: старые данные, гонка, мутация, не тот элемент после фильтрации.
  2. Безопасность и утечки: XSS, интервал без очистки.
  3. Устройство кода: производное состояние, дублирование запроса, отсутствие статусов загрузки.
  4. Читаемость и мелочи: имена, типы, вынос логики в хук. Их называйте коротко, если осталось время.

По каждой проблеме держите одну схему: что не так, к чему это приведёт и как исправить. Если не уверены, скажите это и предложите, как проверить.

Как формулировать замечания

Интервьюер часто играет роль автора кода. Он смотрит, как вы будете общаться с коллегой на настоящем ревью. Говорите о коде, а не о человеке.

Так лучше не говорить

«Тут всё неправильно, кто так пишет эффекты?»

Так звучит хорошо

«Вижу, что в первом эффекте пустые зависимости, а используется postId. Если статья сменится без перемонтирования, останутся старые комментарии. Предлагаю добавить postId и отменять прошлый запрос в очистке. Как думаете, такой сценарий у нас возможен?»

Вопрос в конце не слабость. На реальном ревью у автора может быть контекст, которого вы не знаете. Интервьюеры это ценят.

Исправленная версия

type Comment = { id: number; author: string; text: string };
type Status = 'loading' | 'ready' | 'error';

function useComments(postId: number) {
  const [comments, setComments] = useState<Comment[]>([]);
  const [status, setStatus] = useState<Status>('loading');

  useEffect(() => {
    let controller = new AbortController();

    async function load() {
      controller.abort();
      const current = new AbortController();
      controller = current;
      try {
        const r = await fetch(`/api/posts/${postId}/comments`, { signal: current.signal });
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        setComments(await r.json());
        setStatus('ready');
      } catch {
        if (!current.signal.aborted) setStatus('error');
      }
    }

    setStatus('loading');
    load();
    const id = setInterval(load, 10000);
    return () => {
      clearInterval(id);
      controller.abort();
    };
  }, [postId]);

  return { comments, setComments, status };
}

export function Comments({ postId }: { postId: number }) {
  const { comments, setComments, status } = useComments(postId);
  const [query, setQuery] = useState('');
  const filtered = comments.filter((c) => c.author.includes(query));

  function addLocal(author: string) {
    setComments((prev) => [...prev, { id: Date.now(), author, text: '' }]);
  }

  if (status === 'loading' && comments.length === 0) return <p>Загружаем комментарии</p>;
  if (status === 'error' && comments.length === 0) return <p>Не удалось загрузить комментарии</p>;

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <button onClick={() => addLocal('Я')}>Добавить</button>
      {filtered.map((c) => (
        <div key={c.id}>
          <b>{c.author}</b>
          <p>{c.text}</p>
        </div>
      ))}
    </div>
  );
}

Что изменилось: загрузка вынесена в хук, у эффекта полные зависимости и очистка, старый запрос отменяется перед новым, состояние обновляется без мутации, key стабильный, текст выводится как текст, а не HTML. Если по задаче нужен именно HTML, его очищают санитайзером. Фильтр считается при рендере.

Писать такую версию целиком на собеседовании обычно не требуют. Достаточно назвать изменения и показать одно-два исправления в коде.

Частые ошибки

  • Начинать с отступов и названий переменных, пропустив гонку и утечку
  • Называть проблему без последствий: «тут key неправильный», но не объяснить, что сломается
  • Переписывать компонент целиком молча вместо разбора
  • Оценивать автора, а не код
  • Не заметить безопасность: dangerouslySetInnerHTML с пользовательскими данными

Чек-лист

  • Проверяете у каждого эффекта зависимости и очистку
  • Ищете асинхронные гонки при смене пропсов
  • Проверяете, что state не мутируется
  • Смотрите на key в списках
  • Ищете вставку HTML и пользовательские данные
  • Спрашиваете себя, что увидит пользователь при загрузке и ошибке

Вопросы из базы Техпрогона

Частые вопросы по теме с ответом и разбором. Откройте любой и попробуйте ответить сами.

Как тренировать в Техпрогоне

  • В каталоге React есть вопросы на те же проблемы: гонки запросов, очистка эффектов, key, производное состояние, мутация state.
  • Задачи на React с автопроверкой решаются прямо в браузере, например отмена запроса через AbortController.
  • Билеты с зачётом по React и по браузеру и безопасности: 20 вопросов, зачёт при двух ошибках и меньше.
  • Бесплатно доступны 3 задачи с кодом в день и первые 3 билета каждого трека. Без лимитов в тарифе «Рывок».

Частые вопросы

Как часто на frontend-собеседовании дают код-ревью?
Это один из распространённых форматов наряду с теорией и лайвкодингом. Чаще встречается на собеседованиях middle и выше, но и junior могут попросить найти ошибки в небольшом компоненте.
Нужно ли найти все проблемы?
Не обязательно. Важнее найти главные баги и объяснить их последствия. Если время кончается, назовите оставшиеся темы коротко: «ещё посмотрел бы на обработку ошибок и типы».
Можно ли исправлять код во время ревью?
Спросите интервьюера. Чаще ждут устного разбора и пары точечных исправлений, а не переписывания целиком.
Что делать, если не уверен, баг это или нет?
Скажите честно, опишите сценарий, при котором это сломается, и предложите проверку: тест, запуск или вопрос автору. Это нормальная часть реального ревью.

Билеты с зачётом вместо чтения статей

20 вопросов в билете, зачёт при двух ошибках и меньше, разбор каждой ошибки. Бесплатный тариф без ограничения по времени.

Читайте также