Debugging React: от теории к практике

#react#debugging#frontend#performance

TL;DR

Отладка React-приложений требует особого подхода из-за виртуального DOM и реактивности. Рассмотрим практические техники: от console.log до advanced DevTools, разберём антипаттерны и покажем, как правильно анализировать ререндеры.

Введение: почему React debugging — это отдельный скилл

Типичный сценарий: компонент не обновляется, state ведёт себя странно, а перфоманс падает после “невинных” изменений. В отличие от императивного кода, React требует понимания:

// Классический пример "замыкания в хуках"
function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
    console.log(count); // Всегда отстаёт на 1
  };

  return <button onClick={handleClick}>{count}</button>;
}

Основные техники отладки

1. Инструменты разработчика

React DevTools предоставляют:

Pro tip: Используйте выделение компонентов прямо в DOM (кнопка в левом верхнем углу DevTools).

2. Контрольные точки (breakpoints) в useEffect

useEffect(() => {
  // debugger; // Раскомментируйте для остановки
  fetchData().then(setData);
}, [deps]);

3. Логирование с “метками времени”

console.log('[MyComponent] render:', { 
  props,
  state: unstable_batchedUpdates(() => state) 
});

Разбор реальных кейсов

Кейс 1: Бесконечный луп ререндеров

function UserList() {
  const [users, setUsers] = useState([]);
  const [filter, setFilter] = useState('');

  const filteredUsers = users.filter(u => 
    u.name.includes(filter)
  );

  useEffect(() => {
    fetchUsers().then(setUsers);
  }, [filteredUsers]); // Ошибка: зависимость изменяется при каждом рендере
}

Решение: Вынесите фильтрацию в useMemo или переместите в эффект.

Кейс 2: “Потерянный” контекст в асинхронных операциях

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

  const handleSearch = async () => {
    // query здесь "заморожен" на момент создания функции
    const results = await search(query);
    setResults(results);
  };

  return <input onChange={e => setQuery(e.target.value)} />;
}

Решение: Используйте ref для актуального значения или переместите вызов в эффект.

Инструменты для продвинутой отладки

  1. useWhyDidYouUpdate
    Кастомный хук для логирования изменившихся пропсов:
function useWhyDidYouUpdate(name, props) {
  const prevProps = useRef();
  
  useEffect(() => {
    if (prevProps.current) {
      const changes = {};
      Object.keys({ ...prevProps.current, ...props }).forEach(key => {
        if (prevProps.current[key] !== props[key]) {
          changes[key] = { from: prevProps.current[key], to: props[key] };
        }
      });
      if (Object.keys(changes).length) {
        console.log('[why-did-you-update]', name, changes);
      }
    }
    prevProps.current = props;
  });
}
  1. React Strict Mode
    Помогает выявить:

Заключение: культура отладки

  1. Всегда воспроизводите баг в изолированной среде (CodeSandbox, StackBlitz)
  2. Используйте source maps даже в production
  3. Ведите “журнал багов” с анализом root cause

Финальный совет: Настройте Sentry/LogRocket для мониторинга ошибок в проде с полным контекстом стейта.

Debugging — это не про исправление кода, а про понимание системы. Чем глубже вы разберётесь в механике React, тем быстрее будете находить root cause проблем.


Источник: https://www.reddit.com/r/reactjs/comments/1srfe09/debugging_react_is_a_skill_i_built_a_place_to/