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

Как выполнить функцию перед монтированием компонента с помощью useEffect?

2.2 Middle🔥 263 комментариев
#React

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

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

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

Как выполнить функцию перед монтированием компонента с помощью useEffect?

В React нет встроенной возможности выполнить код ДО монтирования компонента через useEffect, так как useEffect вызывается ПОСЛЕ рендеринга. Однако есть несколько способов решить эту проблему.

Важная деталь: когда выполняется код

// Порядок выполнения:

// 1. Компонент инициализируется
function MyComponent() {
  console.log('1. Инициализация компонента');
  
  // 2. Телo функции (это выполняется ДО рендера!)
  const [count, setCount] = useState(0);
  console.log('2. Body компонента выполнен');
  
  // 3. useEffect - ПОСЛЕ рендера
  useEffect(() => {
    console.log('4. useEffect выполнен (ПОСЛЕ рендера)');
  }, []);
  
  return <div>Count: {count}</div>;
  // console.log('3. Компонент смонтирован и отрендерен');
}

// Реальный вывод:
// 1. Инициализация компонента
// 2. Body компонента выполнен
// 3. Компонент смонтирован и отрендерен
// 4. useEffect выполнен (ПОСЛЕ рендера)

Способ 1: Код в теле компонента (ДО рендера)

Если нужно выполнить код ДО монтирования, пиши прямо в теле компонента:

function MyComponent() {
  // Это выполнится ДО рендера компонента
  const initialValue = calculateInitialValue();
  const [value, setValue] = useState(initialValue);
  
  // Это выполнится ДО рендера
  console.log('Компонент еще не смонтирован, но этот код уже выполнен');
  
  return <div>{value}</div>;
}

Осторожность: этот код выполняется при КАЖДОМ рендере!

// Плохо - каждый рендер пересчитывает
function Component() {
  const expensiveValue = expensiveCalculation(); // вызывается каждый рендер
  const [state, setState] = useState(expensiveValue);
}

// Хорошо - ленивая инициализация
function Component() {
  const [state, setState] = useState(() => expensiveCalculation());
}

Способ 2: useEffect с пустым массивом зависимостей

Это выполнится один раз после монтирования:

import { useEffect, useState } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Выполнится один раз после монтирования
    console.log('Компонент смонтирован!');
    
    async function loadData() {
      const response = await fetch('/api/data');
      const result = await response.json();
      setData(result);
      setLoading(false);
    }
    
    loadData();
  }, []); // Пустой массив = выполнить один раз
  
  if (loading) return <p>Loading...</p>;
  return <div>{data}</div>;
}

Как это работает:

  1. Компонент монтируется (рендерится с loading=true)
  2. После рендера useEffect выполняется
  3. Загружаются данные
  4. setData вызывает повторный рендер с новыми данными

Способ 3: Cleanup функция (перед размонтированием)

function MyComponent() {
  useEffect(() => {
    // Выполнится после монтирования
    console.log('Компонент смонтирован!');
    
    // Cleanup функция - выполнится перед размонтированием
    return () => {
      console.log('Компонент размонтируется!');
    };
  }, []);
  
  return <div>Component</div>;
}

Использование для очистки ресурсов:

function ChatComponent({ userId }) {
  useEffect(() => {
    // После монтирования - подписаться на сообщения
    const unsubscribe = subscribeToMessages(userId, (msg) => {
      console.log('Получено сообщение:', msg);
    });
    
    // Перед размонтированием - отписаться
    return () => {
      unsubscribe();
    };
  }, [userId]);
  
  return <div>Chat</div>;
}

Способ 4: Инициализация через useState с функцией

Этот код выполнится ДО рендера (только один раз):

function MyComponent() {
  // Функция выполнится только один раз при инициализации!
  const [state, setState] = useState(() => {
    console.log('Инициализация state (ДО рендера)');
    return calculateInitialValue();
  });
  
  return <div>{state}</div>;
}

// Vs без функции (выполняется каждый рендер)
function MyComponent() {
  const [state, setState] = useState(calculateInitialValue()); // каждый рендер!
}

Способ 5: Custom Hook для выполнения кода

// hooks/useMount.ts
import { useEffect } from 'react';

export function useMount(callback: () => void | (() => void)) {
  useEffect(() => {
    return callback();
  }, []);
}

// Использование
function MyComponent() {
  useMount(() => {
    console.log('Компонент смонтирован');
    
    return () => {
      console.log('Компонент размонтирован');
    };
  });
  
  return <div>Component</div>;
}

Практические примеры

Пример 1: Загрузка данных при монтировании

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    // Выполнится один раз после монтирования
    async function fetchUser() {
      try {
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        setUser(data);
      } catch (err) {
        setError(err.message);
      }
    }
    
    fetchUser();
  }, [userId]); // Перезагрузить, если userId изменится
  
  if (!user) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;
  
  return <div>{user.name}</div>;
}

Пример 2: Подписка на события

function WindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });
  
  useEffect(() => {
    // После монтирования - добавить слушатель
    function handleResize() {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    }
    
    window.addEventListener('resize', handleResize);
    
    // Перед размонтированием - удалить слушатель
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // Только один раз
  
  return (
    <div>
      {size.width} x {size.height}
    </div>
  );
}

Пример 3: Запуск таймера

function Countdown({ initialSeconds = 10 }) {
  const [seconds, setSeconds] = useState(initialSeconds);
  
  useEffect(() => {
    // После монтирования - запустить таймер
    if (seconds <= 0) return;
    
    const timer = setInterval(() => {
      setSeconds((prev) => prev - 1);
    }, 1000);
    
    // Перед размонтированием (или перед следующим эффектом) - очистить таймер
    return () => clearInterval(timer);
  }, [seconds]); // Перезапустить при изменении seconds
  
  return <div>{seconds} секунд</div>;
}

Частые ошибки

Ошибка 1: Забыли массив зависимостей

// Плохо - выполнится после КАЖДОГО рендера
useEffect(() => {
  fetch('/api/data');
});

// Хорошо - выполнится один раз
useEffect(() => {
  fetch('/api/data');
}, []);

Ошибка 2: Забыли cleanup функцию

// Плохо - утечка памяти, подписки остаются активными
useEffect(() => {
  element.addEventListener('click', handleClick);
}, []);

// Хорошо - удалить слушатель при размонтировании
useEffect(() => {
  element.addEventListener('click', handleClick);
  return () => element.removeEventListener('click', handleClick);
}, []);

Ошибка 3: Забыли переменную в зависимостях

// Плохо - эффект не будет перезапускаться при изменении userId
useEffect(() => {
  fetch(`/api/users/${userId}`);
}, []); // Забыли userId!

// Хорошо
useEffect(() => {
  fetch(`/api/users/${userId}`);
}, [userId]); // userId в зависимостях

Резюме: Порядок выполнения

1. Инициализация состояния (useState) - ДО рендера
2. Телo функции компонента - ДО рендера
3. Рендер компонента - DOM обновляется
4. useEffect выполняется - ПОСЛЕ рендера
5. Cleanup функция при размонтировании

Заключение

useEffect не может выполниться ДО монтирования, потому что монтирование — это то же самое, что первый рендер. Если нужна логика ДО рендера, пиши прямо в теле компонента. Если нужна логика ПОСЛЕ монтирования (загрузка данных, подписки), используй useEffect с пустым массивом зависимостей []. Всегда не забывай cleanup функцию для очистки ресурсов.