TL;DR
Отладка React-приложений требует особого подхода из-за виртуального DOM и реактивности. Рассмотрим практические техники: от console.log до advanced DevTools, разберём антипаттерны и покажем, как правильно анализировать ререндеры.
Введение: почему React debugging — это отдельный скилл
Типичный сценарий: компонент не обновляется, state ведёт себя странно, а перфоманс падает после “невинных” изменений. В отличие от императивного кода, React требует понимания:
- Фаз жизненного цикла
- Механизма реконсиляции
- Особенностей closure в хуках
// Классический пример "замыкания в хуках"
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 для актуального значения или переместите вызов в эффект.
Инструменты для продвинутой отладки
- 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;
});
}
- React Strict Mode
Помогает выявить:
- Нечистые рендеры
- Утечки памяти
- Устаревшие API
Заключение: культура отладки
- Всегда воспроизводите баг в изолированной среде (CodeSandbox, StackBlitz)
- Используйте source maps даже в production
- Ведите “журнал багов” с анализом 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/