Расскажите о случае, когда вы улучшали performance, accessibility, testing или maintainability в проекте на React.
Опишите конкретный случай: что измерили (Lighthouse, Coverage, axe), что изменили (мемоизация, lazy, семантика, тесты) и каков был измеримый результат — LCP, покрытие, оценка доступности.
Подход к улучшению качества React-проекта
Улучшение любого аспекта — performance, accessibility, testing или maintainability — начинается с измерения исходного состояния. Без базовых метрик невозможно оценить результат. Ниже разобран реальный сценарий по каждому направлению.
Performance: устранение лишних ре-рендеров
На странице дашборда с таблицей из 500 строк замечены подвисания при фильтрации. Инструменты диагностики: React DevTools Profiler (вкладка «Flamegraph») и Chrome Performance panel.
// До: родитель ре-рендерится → все строки ре-рендерятся
function Dashboard({ data }: { data: Row[] }) {
const [filter, setFilter] = useState("");
const filtered = data.filter(r => r.name.includes(filter));
return <Table rows={filtered} onSort={col => { /* inline */ }} />;
}
// После: стабильные ссылки + мемоизация дочернего
const MemoTable = memo(Table);
function Dashboard({ data }: { data: Row[] }) {
const [filter, setFilter] = useState("");
const filtered = useMemo(
() => data.filter(r => r.name.includes(filter)),
[data, filter]
);
const handleSort = useCallback((col: string) => { /* ... */ }, []);
return <MemoTable rows={filtered} onSort={handleSort} />;
}
Результат: время рендера при вводе фильтра снизилось с 180 мс до 12 мс по данным Profiler.
Accessibility: аудит и правки
Запуск axe-core через @axe-core/react в dev-режиме выявил: отсутствие aria-label у иконок, некорректный heading order, отсутствие role="alert" у toast-уведомлений.
// Было
<button onClick={onClose}><CloseIcon /></button>
// Стало
<button onClick={onClose} aria-label="Закрыть диалог">
<CloseIcon aria-hidden="true" />
</button>
// Toast с правильным role
<div role="alert" aria-live="polite">{message}</div>
После правок Lighthouse Accessibility score вырос с 71 до 98.
Testing: переход с enzyme на React Testing Library
Старые тесты проверяли внутреннее состояние компонентов через wrapper.state() — хрупкий подход, ломавшийся при любом рефакторинге.
// Было (enzyme, тест внутренней реализации)
expect(wrapper.state("isOpen")).toBe(true);
// Стало (RTL, тест поведения)
import { render, screen, userEvent } from "@testing-library/react";
test("открывает меню по клику", async () => {
const user = userEvent.setup();
render(<Dropdown label="Меню" items={["Профиль", "Выход"]} />);
await user.click(screen.getByRole("button", { name: "Меню" }));
expect(screen.getByRole("menuitem", { name: "Профиль" })).toBeVisible();
});
Покрытие выросло с 34% до 78%, при этом тесты стали устойчивее к рефакторингу.
Maintainability: выделение кастомных хуков
Логика загрузки данных дублировалась в 12 компонентах. Вынесена в useFetch:
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<Error | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(r => r.json())
.then(setData)
.catch(e => { if (e.name !== "AbortError") setError(e); })
.finally(() => setLoading(false));
return () => controller.abort();
}, [url]);
return { data, error, loading };
}
Подводные камни
- memo без useCallback — обернуть компонент в
memo, но передавать inline-функцию prop'ом бессмысленно: новая ссылка на каждый рендер ломает мемоизацию. - useMemo для всего подряд — мемоизация сама по себе имеет стоимость; профилируйте перед применением, не добавляйте «на всякий случай».
- axe-core ловит не все a11y-проблемы — автоматические инструменты находят ~30–40% нарушений WCAG; ручное тестирование с клавиатурой и скринридером обязательно.
- Testing Library и async — забыть
awaitпередuser.click()изuserEvent.setup()приводит к непредсказуемым падениям тестов. - Coverage как цель — 100% coverage не равно качеству тестов; важнее тестировать критические сценарии, а не гнаться за числом.
- Рефакторинг без тестов — вынос логики в хуки без предварительных тестов превращает улучшение maintainability в источник регрессий.
- AbortController не везде — без отмены запроса при размонтировании компонента возможен
setState on unmounted component— утечка памяти.
What hurts your answer
- Выдумывать опыт или говорить слишком общими фразами
- Не объяснять свою личную роль в работе с React
- Не показывать результат, метрики или извлечённые уроки
What they're listening for
- Может подготовить честный пример использования React
- Показывает свою роль, решения и результат
- Умеет рефлексировать над trade-offs и уроками