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

Какой может быть побочный эффект в useEffect помимо запроса на сервер?

2.0 Middle🔥 291 комментариев
#React

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

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

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

Побочные эффекты в useEffect

Побочный эффект (side effect) — это любое действие, которое влияет на что-либо вне компонента или его чистых функций. useEffect в React используется не только для запросов на сервер, но и для множества других побочных эффектов.

Примеры побочных эффектов

1. Работа с DOM напрямую

import { useEffect, useRef } from 'react';

function TextInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    // Побочный эффект: манипуляция DOM
    inputRef.current?.focus();
    inputRef.current?.select();
  }, []);

  return <input ref={inputRef} defaultValue="Focus me" />;
}

2. Управление локальным хранилищем (localStorage/sessionStorage)

function UserPreferences() {
  const [theme, setTheme] = useState('light');

  // Побочный эффект: запись в localStorage
  useEffect(() => {
    localStorage.setItem('theme', theme);
  }, [theme]);

  // Побочный эффект: чтение из localStorage
  useEffect(() => {
    const savedTheme = localStorage.getItem('theme');
    if (savedTheme) {
      setTheme(savedTheme);
    }
  }, []);

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Toggle Theme: {theme}
    </button>
  );
}

3. Подписка на события (Event Listeners)

function WindowResizeListener() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    // Побочный эффект: добавление слушателя события
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    }

    window.addEventListener('resize', handleResize);

    // Cleanup функция: удаление слушателя
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return <p>Window size: {windowSize.width}x{windowSize.height}</p>;
}

4. Работа с таймерами (setTimeout, setInterval)

function StopWatch() {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(false);

  useEffect(() => {
    if (!isRunning) return;

    // Побочный эффект: запуск интервала
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

    // Cleanup: очистка интервала
    return () => clearInterval(interval);
  }, [isRunning]);

  return (
    <div>
      <p>Time: {seconds}s</p>
      <button onClick={() => setIsRunning(!isRunning)}>
        {isRunning ? 'Stop' : 'Start'}
      </button>
    </div>
  );
}

5. Работа с Geolocation API

function LocationTracker() {
  const [location, setLocation] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Побочный эффект: запрос текущего местоположения
    if (!navigator.geolocation) {
      setError('Geolocation not supported');
      return;
    }

    navigator.geolocation.getCurrentPosition(
      position => {
        setLocation({
          lat: position.coords.latitude,
          lng: position.coords.longitude
        });
      },
      error => setError(error.message)
    );
  }, []);

  return location ? (
    <p>Location: {location.lat}, {location.lng}</p>
  ) : (
    <p>{error || 'Getting location...'}</p>
  );
}

6. Подписка на Observable или EventEmitter

import { Subject } from 'rxjs';

const messageSubject = new Subject();

function MessageSubscriber() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    // Побочный эффект: подписка на Observable
    const subscription = messageSubject.subscribe(message => {
      setMessages(prev => [...prev, message]);
    });

    // Cleanup: отписка
    return () => subscription.unsubscribe();
  }, []);

  return (
    <ul>
      {messages.map((msg, i) => <li key={i}>{msg}</li>)}
    </ul>
  );
}

7. Работа с медиа (Camera, Microphone)

function VideoStream() {
  const videoRef = useRef(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Побочный эффект: запрос доступа к камере
    async function startCamera() {
      try {
        const stream = await navigator.mediaDevices.getUserMedia({
          video: true
        });
        if (videoRef.current) {
          videoRef.current.srcObject = stream;
        }
      } catch (err) {
        setError(err.message);
      }
    }

    startCamera();

    // Cleanup: остановка потока
    return () => {
      if (videoRef.current?.srcObject) {
        videoRef.current.srcObject
          .getTracks()
          .forEach(track => track.stop());
      }
    };
  }, []);

  return error ? <p>{error}</p> : <video ref={videoRef} autoPlay />;
}

8. Изменение документа или класса body

function DarkModeToggle() {
  const [isDark, setIsDark] = useState(false);

  useEffect(() => {
    // Побочный эффект: изменение класса на body
    if (isDark) {
      document.body.classList.add('dark-mode');
      document.title = 'Dark Mode';
    } else {
      document.body.classList.remove('dark-mode');
      document.title = 'Light Mode';
    }
  }, [isDark]);

  return (
    <button onClick={() => setIsDark(!isDark)}>
      {isDark ? 'Light' : 'Dark'}
    </button>
  );
}

9. Работа с IndexedDB

function DatabaseCache() {
  const [data, setData] = useState(null);

  useEffect(() => {
    // Побочный эффект: работа с IndexedDB
    const openRequest = indexedDB.open('MyDB', 1);

    openRequest.onsuccess = () => {
      const db = openRequest.result;
      const transaction = db.transaction('items');
      const store = transaction.objectStore('items');
      const request = store.getAll();

      request.onsuccess = () => {
        setData(request.result);
      };
    };
  }, []);

  return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
}

10. Аналитика и трекинг

function PageTracker() {
  useEffect(() => {
    // Побочный эффект: отправка аналитики
    window.gtag?.('event', 'page_view', {
      page_path: window.location.pathname
    });
  }, []);

  return <div>Page loaded</div>;
}

11. Синхронизация между вкладками

function TabSync() {
  const [value, setValue] = useState('');

  useEffect(() => {
    // Побочный эффект: слушание событий от других вкладок
    const handleStorage = (e) => {
      if (e.key === 'sharedValue') {
        setValue(e.newValue || '');
      }
    };

    window.addEventListener('storage', handleStorage);

    return () => window.removeEventListener('storage', handleStorage);
  }, []);

  const handleChange = (e) => {
    const newValue = e.target.value;
    setValue(newValue);
    localStorage.setItem('sharedValue', newValue);
  };

  return <input value={value} onChange={handleChange} />;
}

Правила для побочных эффектов

1. Всегда очищай за собой (Cleanup)

useEffect(() => {
  // Setup
  const subscription = source.subscribe(handler);
  const timer = setInterval(tick, 1000);
  window.addEventListener('click', handler);

  // Cleanup функция
  return () => {
    subscription.unsubscribe();
    clearInterval(timer);
    window.removeEventListener('click', handler);
  };
}, []);

2. Управляй зависимостями

// Выполнится один раз при монтировании
useEffect(() => {
  console.log('Mounted');
}, []);

// Выполнится когда변경 userId
useEffect(() => {
  fetchUser(userId);
}, [userId]);

// Выполнится после каждого рендера
useEffect(() => {
  console.log('Re-rendered');
});

Заключение

Побочные эффекты в useEffect — это не только запросы на сервер. Это работа с DOM, localStorage, событиями, таймерами, геолокацией, медиа, аналитикой и многим другим. Ключевой момент — всегда очищай ресурсы в cleanup функции, чтобы избежать утечек памяти и неправильного поведения приложения. Правильное управление побочными эффектами — это основа стабильного и производительного React приложения.