useEffect — cleanup funksiya, nima uchun kerak

~15 daq · React · Hooks — Asosiy

Muammo: "qoldirilgan" ishlar

useEffect ba'zan uzoq davom etadigan ishlarni boshlaydi — timer, event listener, WebSocket ulanish. Komponent ekrandan ketganda bu ishlar to'xtatilmasa — muammo bo'ladi.

Tasavvur qiling: komponent har soniyada server dan ma'lumot oladi. Foydalanuvchi boshqa sahifaga o'tdi — komponent yo'qoldi. Lekin timer hali ishlayapti, hali server ga so'rov yuborayapti. Komponent yo'q, lekin so'rovlar ketayapti. Bu — memory leak.

Komponent paydo bo'ldi → timer boshlandi (har 1 sek)
Foydalanuvchi ketdi  → komponent yo'qoldi
                       lekin timer hali ishlayapti! ❌
                       → xotira sarflanmoqda
                       → konsolda xato: "Can't update state on unmounted component"

useEffect ning cleanup funksiyasi aynan shu muammoni hal qiladi.


Cleanup funksiya qanday yoziladi?

useEffect callback funksiya qaytarishi mumkin. Bu qaytarilgan funksiya — cleanup.

useEffect(() => {
  // ish boshlash
  const timer = setInterval(() => {
    console.log('ishlayapti');
  }, 1000);

  // cleanup — komponent ketganda yoki effect qayta ishlaganda
  return () => {
    clearInterval(timer); // timerni to'xtatish
  };
}, []);

Cleanup qachon chaqiriladi?

┌─────────────────────────────────────────────┐
│  Komponent paydo bo'ldi (mount)             │
│  → useEffect ishladi                        │
│  → timer boshlandi                          │
└──────────────────┬──────────────────────────┘
                   │
        (foydalanuvchi boshqa sahifaga o'tdi)
                   │
                   ▼
┌─────────────────────────────────────────────┐
│  Komponent yo'qoldi (unmount)               │
│  → cleanup ishladi                          │
│  → timer to'xtatildi ✅                     │
└─────────────────────────────────────────────┘

Dependency bor bo'lsa — cleanup har safar effect qayta ishlaganda ham chaqiriladi:

┌──────────────────────────────────────────────┐
│  userId = 1 → effect ishladi, so'rov boshlandi│
└──────────────────┬───────────────────────────┘
                   │ userId = 2 ga o'zgardi
                   ▼
┌──────────────────────────────────────────────┐
│  Cleanup: eski so'rov bekor qilindi          │
│  Yangi effect: userId=2 uchun so'rov boshlandi│
└──────────────────────────────────────────────┘

Amaliy misollar

Event listener

function OynaTahlili() {
  const [kenglik, setKenglik] = useState(window.innerWidth);

  useEffect(() => {
    function yangilash() {
      setKenglik(window.innerWidth);
    }

    window.addEventListener('resize', yangilash);

    return () => {
      window.removeEventListener('resize', yangilash); // tozalash
    };
  }, []);

  return <p>Oyna kengligi: {kenglik}px</p>;
}

Cleanup bo'lmasa — komponent yo'qolgandan keyin ham resize event ishlaydi, yo'q komponentning state ini yangilashga urinadi.

AbortController — so'rovni bekor qilish

function UserProfil({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    fetch(`/api/users/${userId}`, { signal: controller.signal })
      .then(r => r.json())
      .then(setUser)
      .catch(err => {
        if (err.name === 'AbortError') return; // bekor qilindi — normal
        console.error(err);
      });

    return () => {
      controller.abort(); // komponent ketsa yoki userId o'zgarse — so'rovni bekor qil
    };
  }, [userId]);

  if (!user) return <p>Yuklanmoqda...</p>;
  return <h2>{user.name}</h2>;
}

Nima uchun kerak? userId tez o'zgarsa — masalan, foydalanuvchi tez-tez bosib tursa — bir nechta so'rov ketadi. Qaysi biri oxirgi kelishini bilmaysiz. Eski so'rovlar bekor qilinmasa — eski ma'lumot yangi ma'lumotni ustiga yozishi mumkin.

WebSocket

function ChatXonasi({ xonaId }) {
  const [xabarlar, setXabarlar] = useState([]);

  useEffect(() => {
    const ws = new WebSocket(`wss://chat.example.com/${xonaId}`);

    ws.onmessage = (event) => {
      setXabarlar(prev => [...prev, JSON.parse(event.data)]);
    };

    return () => {
      ws.close(); // ulanishni yopish
    };
  }, [xonaId]); // xona o'zgarganda — eski ulanish yopiladi, yangi ochiladi

  return (
    <ul>
      {xabarlar.map((x, i) => <li key={i}>{x.matn}</li>)}
    </ul>
  );
}

StrictMode va cleanup

Development rejimida React StrictMode bilan ishlayotganda — useEffect ikki marta ishlaydi (mount → cleanup → mount). Bu ataylab qilingan — cleanup to'g'ri yozilganini tekshirish uchun.

StrictMode (development):
  Mount → effect ishladi
  Cleanup → effect tozalandi
  Mount → effect qayta ishladi  ← ikkinchi marta

Production:
  Mount → effect ishladi  ← faqat bir marta

Agar cleanup to'g'ri yozilmagan bo'lsa — bu ikki marta ishlatish muammoni ko'rsatadi. Production da bunday bo'lmaydi, lekin muammo yashirinib qoladi.


Xulosa

useEffect(() => {
  // ish boshlash: timer, listener, WebSocket, so'rov

  return () => {
    // cleanup: to'xtatish, o'chirish, bekor qilish
  };
}, []);

Cleanup kerak bo'lgan holatlar:

  • setInterval / setTimeout → clearInterval / clearTimeout
  • addEventListener → removeEventListener
  • fetch → AbortController.abort()
  • WebSocket → ws.close()

Qoida: useEffect biror narsa boshlaса — cleanup o'sha narsani to'xtatishi kerak.


Keyingi dars

12-dars: useEffect — asinxron funksiya, fetch bilan ishlash