ReactMiddleExperience

Расскажите о случае, когда вы улучшали 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 и уроками

Related topics

Расскажите о случае, когда вы улучшали performance, accessibility, testing или maintainability в проекте на React. | Talanto