← Назад к вопросам

Зачем нужно размонтирование в React?

2.3 Middle🔥 271 комментариев
#React#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Зачем нужно размонтирование в React?

Размонтирование (unmounting) в React — это процесс удаления компонента из DOM. Это критически важно для корректной очистки ресурсов, предотвращения утечек памяти и избежания побочных эффектов, которые продолжают работать после удаления компонента. React предоставляет хуки и методы для контролирования процесса размонтирования.

Жизненный цикл компонента

Размонтирование — это последняя фаза жизненного цикла:

Монтирование → Обновление → Размонтирование
  (render)      (re-render)    (cleanup)
function Component() {
  // 1. МОНТИРОВАНИЕ (первый рендер)
  console.log('Component mounted');
  
  // 2. ОБНОВЛЕНИЕ (если пропсы или состояние изменились)
  // console.log('Component updated');
  
  // 3. РАЗМОНТИРОВАНИЕ (удаление из DOM)
  // cleanup функция сработает
  
  return <div>Hello</div>;
}

Очистка ресурсов в useEffect

Основной способ управления размонтированием — это функция очистки в useEffect:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Монтирование: загрузить данные пользователя
    let isMounted = true; // Флаг для отслеживания жизненного цикла
    
    async function fetchUser() {
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      
      // Установить состояние только если компонент ещё смонтирован
      if (isMounted) {
        setUser(data);
        setLoading(false);
      }
    }
    
    fetchUser();
    
    // Размонтирование: очистка
    return () => {
      isMounted = false; // Отметить, что компонент размонтирован
      // Здесь выполнится очистка
    };
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  return <div>{user?.name}</div>;
}

Отписка от подписок

function RealtimeChat() {
  const [messages, setMessages] = useState([]);
  
  useEffect(() => {
    // Монтирование: подписаться на обновления
    const unsubscribe = subscribeToChat((newMessage) => {
      setMessages(prev => [...prev, newMessage]);
    });
    
    // Размонтирование: отписаться
    return () => {
      unsubscribe(); // Прекратить слушать сообщения
    };
  }, []);
  
  return (
    <div>
      {messages.map(msg => <div key={msg.id}>{msg.text}</div>)}
    </div>
  );
}

Очистка таймеров и интервалов

function Timer() {
  const [seconds, setSeconds] = useState(0);
  
  useEffect(() => {
    // Монтирование: создать интервал
    const intervalId = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);
    
    // Размонтирование: очистить интервал
    return () => {
      clearInterval(intervalId); // Предотвратить утечку памяти
    };
  }, []);
  
  return <div>{seconds}s</div>;
}

// ВАЖНО: без очистки интервал продолжает работать в памяти даже
// после размонтирования компонента, вызывая утечку памяти

Очистка слушателей событий

function WindowResizeHandler() {
  const [width, setWidth] = useState(window.innerWidth);
  
  useEffect(() => {
    // Монтирование: добавить слушатель
    const handleResize = () => {
      setWidth(window.innerWidth);
    };
    
    window.addEventListener('resize', handleResize);
    
    // Размонтирование: удалить слушатель
    return () => {
      window.removeEventListener('resize', handleResize);
      // Без этого слушатель остаётся в памяти
    };
  }, []);
  
  return <div>Width: {width}px</div>;
}

Отмена запросов

function SearchResults({ query }) {
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  
  useEffect(() => {
    setLoading(true);
    
    // Создать AbortController для отмены запроса
    const controller = new AbortController();
    
    // Монтирование: отправить запрос
    fetch(`/api/search?q=${query}`, {
      signal: controller.signal
    })
      .then(res => res.json())
      .then(data => {
        // Результаты придут после размонтирования
        // попытаются обновить состояние несуществующего компонента
        setResults(data);
        setLoading(false);
      })
      .catch(error => {
        if (error.name === 'AbortError') {
          console.log('Request was cancelled');
        }
      });
    
    // Размонтирование: отменить запрос
    return () => {
      controller.abort(); // Отменить fetch запрос
    };
  }, [query]);
  
  return (
    <div>
      {loading && <div>Searching...</div>}
      {results.map(item => <div key={item.id}>{item.title}</div>)}
    </div>
  );
}

Проблема: Warning о setState после размонтирования

// ❌ ПЛОХО - вызывает warning
function BadComponent() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    setTimeout(() => {
      setData('loaded'); // Может срабатить после размонтирования!
    }, 3000);
  }, []);
  
  return <div>{data}</div>;
}

// ✅ ХОРОШО - с очисткой
function GoodComponent() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    let timeoutId;
    let isMounted = true;
    
    timeoutId = setTimeout(() => {
      if (isMounted) { // Проверка перед setState
        setData('loaded');
      }
    }, 3000);
    
    return () => {
      isMounted = false;
      clearTimeout(timeoutId);
    };
  }, []);
  
  return <div>{data}</div>;
}

Очистка в классовых компонентах

class UserProfile extends React.Component {
  constructor(props) {
    super(props);
    this.state = { user: null };
  }
  
  componentDidMount() {
    // Монтирование
    this.subscription = subscribeToUser(this.props.userId, (user) => {
      this.setState({ user });
    });
  }
  
  componentWillUnmount() {
    // Размонтирование: очистка
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
  
  render() {
    return <div>{this.state.user?.name}</div>;
  }
}

Практический пример: модальное окно

function Modal({ isOpen, onClose, children }) {
  useEffect(() => {
    if (!isOpen) return;
    
    // Монтирование: блокировать скролл
    const originalOverflow = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    
    // Монтирование: слушатель на Escape
    const handleKeyDown = (e) => {
      if (e.key === 'Escape') onClose();
    };
    document.addEventListener('keydown', handleKeyDown);
    
    // Размонтирование: восстановить всё
    return () => {
      document.body.style.overflow = originalOverflow;
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [isOpen, onClose]);
  
  if (!isOpen) return null;
  
  return (
    <div className="modal">
      <button onClick={onClose}>Close</button>
      {children}
    </div>
  );
}

Чеклист очистки ресурсов

При размонтировании убедись, что очищены:

  • ✅ Таймеры (clearTimeout, clearInterval)
  • ✅ Слушатели событий (removeEventListener)
  • ✅ Подписки (unsubscribe)
  • ✅ Сетевые запросы (AbortController)
  • ✅ Web Socket соединения
  • ✅ Глобальные переменные
  • ✅ DOM изменения (стили, классы на document.body)

Итоги

  • Размонтирование — удаление компонента и очистка его ресурсов
  • Критично для предотвращения утечек памяти и побочных эффектов
  • useEffect cleanup function (return () => {...}) срабатывает при размонтировании
  • Обязательная очистка: таймеры, события, подписки, сетевые запросы
  • Warning о setState после размонтирования указывает на пропущенную очистку
  • Это отличает хороший код от плохого в production приложениях
Зачем нужно размонтирование в React? | PrepBro