Задачи с кодом
Код-ревью на собеседовании: как найти проблемы в React-компоненте
Содержание
Код-ревью на собеседовании это задание «вот компонент, найдите в нём проблемы»: вы читаете чужой код вслух и объясняете, что сломается и как починить. Так проверяют то, что нужно в работе каждый день: умение читать код, видеть баги и спокойно говорить о них. Ниже разобран типичный 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. Неполные зависимости первого эффекта
2. Гонка запросов
3. Интервал без очистки
4. Мутация состояния
5. Индекс в качестве key
6. XSS через dangerouslySetInnerHTML
7. Лишнее состояние для производных данных
8. Нет загрузки и ошибок
В каком порядке отвечать
Не перечисляйте замечания в порядке строк. Сначала скажите, что делает компонент, одной фразой. Так интервьюер поймёт, что вы прочитали замысел. Дальше идите от важного к мелкому.
- Баги, которые увидит пользователь: старые данные, гонка, мутация, не тот элемент после фильтрации.
- Безопасность и утечки: XSS, интервал без очистки.
- Устройство кода: производное состояние, дублирование запроса, отсутствие статусов загрузки.
- Читаемость и мелочи: имена, типы, вынос логики в хук. Их называйте коротко, если осталось время.
По каждой проблеме держите одну схему: что не так, к чему это приведёт и как исправить. Если не уверены, скажите это и предложите, как проверить.
Как формулировать замечания
Интервьюер часто играет роль автора кода. Он смотрит, как вы будете общаться с коллегой на настоящем ревью. Говорите о коде, а не о человеке.
«Тут всё неправильно, кто так пишет эффекты?»
«Вижу, что в первом эффекте пустые зависимости, а используется 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
- XSS и как от него защищатьсяБраузер и DOM
- useEffect: зависимости и очисткаReact
- Где хранить токен авторизацииБраузер и DOM
- useMemo, useCallback, memoReact
- Найди XSS в кодеБраузер и DOM
- Устаревшее замыканиеReact
- Что даёт HttpOnlyБраузер и DOM
- Когда эффект лишнийReact
- Что такое один originБраузер и DOM
Как тренировать в Техпрогоне
- В каталоге React есть вопросы на те же проблемы: гонки запросов, очистка эффектов, key, производное состояние, мутация state.
- Задачи на React с автопроверкой решаются прямо в браузере, например отмена запроса через AbortController.
- Билеты с зачётом по React и по браузеру и безопасности: 20 вопросов, зачёт при двух ошибках и меньше.
- Бесплатно доступны 3 задачи с кодом в день и первые 3 билета каждого трека. Без лимитов в тарифе «Рывок».
Частые вопросы
Как часто на frontend-собеседовании дают код-ревью?
Нужно ли найти все проблемы?
Можно ли исправлять код во время ревью?
Что делать, если не уверен, баг это или нет?
Билеты с зачётом вместо чтения статей
20 вопросов в билете, зачёт при двух ошибках и меньше, разбор каждой ошибки. Бесплатный тариф без ограничения по времени.
Читайте также
Задачи на frontend-собеседовании
debounce, throttle, Promise.all, deepEqual, EventEmitter, flatten и useDebounce: условие, решение на JavaScript, что проверяют и какие будут уточнения.
Что выведет код: задачи JavaScript
Задачи «что выведет консоль» с frontend-собеседований: var и let в цикле, hoisting, this, event loop, приведение типов и ссылки. Вывод и разбор к каждой.
Ошибки на frontend-собеседовании
Типичные ошибки на собеседовании frontend по этапам: заученная теория, молчание на коде, «не знаю» без рассуждения, проекты без своей роли. Как исправить.
Лайвкодинг на собеседовании
Как решать задачу вслух и не зависнуть
Вопросы по React
Эффекты, состояние, рендер и производительность
Все статьи
Первая работа, подготовка к собесу и задачи с кодом