React / useEffect цэвэрлэгээ

useEffect цэвэрлэгээ

Өмнөх хичээлд useEffect-н суурийг тавьж, cleanup функц буцаах аргыг товч үзлээ. Энэ хичээлд cleanup-г гүнзгийрч судална — яагаад заавал хэрэгтэй, хаана орхивол аюултай, бодит хэрэглээний жишээнүүдийг практикаар ойлгоно. Cleanup нь React-н хамгийн анхааралтай байх ёстой сэдвүүдийн нэг юм.

Яагаад cleanup шаардлагатай вэ?

React component дэлгэцнээс гарах (unmount) буюу useEffect дахин ажиллахаас өмнө өмнөх effect-г зогсоох хэрэгтэй. Үгүй бол хуучин code шинэ state-г өөрчлөхийг оролдож, алдаа гаргана:

jsx
import { useState, useEffect } from "react";

function Timer({ isRunning }) {
  const [seconds, setSeconds] = useState(0);

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

    const interval = setInterval(() => {
      setSeconds((prev) => prev + 1);
    }, 1000);

    // ✅ Cleanup — interval-г зогсооно
    return () => clearInterval(interval);
  }, [isRunning]);

  return <p>{seconds} секунд</p>;
}

isRunning false болоход эсвэл component unmount болоход clearInterval дуудагдан interval зогсоно. Cleanup байхгүй бол interval хэдэн ч удаа давхацч ажиллана.

Memory leak гэж юу вэ?

Cleanup хийгээгүй effect нь component устсан хойно ч ажилласаар байж болно. Энэ нь memory leak буюу санах ойн алдагдал юм:

jsx
import { useState, useEffect } from "react";

function SearchResults({ query }) {
  const [results, setResults] = useState([]);

  useEffect(() => {
    let isCancelled = false;

    async function fetchResults() {
      const response = await fetch(`/api/search?q=${query}`);
      const data = await response.json();

      // ✅ Component устсан бол state-г өөрчлөхгүй
      if (!isCancelled) {
        setResults(data);
      }
    }

    fetchResults();

    return () => {
      // Cleanup — fetch дууссан ч state-г өөрчлөхгүй болгоно
      isCancelled = true;
    };
  }, [query]);

  return (
    <ul>
      {results.map((r) => (
        <li key={r.id}>{r.title}</li>
      ))}
    </ul>
  );
}

isCancelled flag ашиглан component устсаны дараа ирсэн хариуг үл тоомсорлодог энэ хэлбэр бол async fetch-тэй ажиллахад нийтлэг шийдэл юм.

Event Listener cleanup

window эсвэл document-д event listener нэмэхдээ cleanup заавал хэрэгтэй:

jsx
import { useState, useEffect } from "react";

function KeyTracker() {
  const [lastKey, setLastKey] = useState("");

  useEffect(() => {
    function handleKeyDown(e) {
      setLastKey(e.key);
    }

    // Сонсогч нэмнэ
    window.addEventListener("keydown", handleKeyDown);

    // ✅ Cleanup — сонсогч устгана
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, []); // Нэг удаа л нэмнэ

  return <p>Сүүлд дарсан товч: {lastKey || "—"}</p>;
}

Cleanup байхгүй бол component дахин рендер болох бүрт шинэ listener нэмэгдэж, нэг даралтад хэдэн ч удаа handleKeyDown дуудагдана.

Subscription cleanup

WebSocket эсвэл бусад subscription-уудыг ч мөн адил цэвэрлэнэ:

jsx
import { useState, useEffect } from "react";

function OnlineStatus({ userId }) {
  const [isOnline, setIsOnline] = useState(false);

  useEffect(() => {
    // WebSocket холболт эхлүүлнэ
    const socket = new WebSocket(`wss://api.example.com/status/${userId}`);

    socket.onmessage = (event) => {
      const data = JSON.parse(event.data);
      setIsOnline(data.online);
    };

    socket.onerror = () => setIsOnline(false);

    // ✅ Cleanup — холболт таслана
    return () => {
      socket.close();
    };
  }, [userId]);

  return (
    <span style={{ color: isOnline ? "#4ade80" : "#94a3b8" }}>
      {isOnline ? "● Онлайн" : "● Оффлайн"}
    </span>
  );
}

Cleanup хэзээ ажилладаг вэ?

jsx
import { useEffect } from "react";

function LifecycleDemo({ count }) {
  useEffect(() => {
    console.log("Effect ажиллав — count:", count);

    return () => {
      // Энэ нь:
      // 1. Component unmount болоход ажиллана
      // 2. Дараагийн effect ажиллахаас ӨМНӨ ажиллана
      console.log("Cleanup ажиллав — count:", count);
    };
  }, [count]);

  return <p>Count: {count}</p>;
}

// count 0 → 1 болоход гарах лог дараалал:
// "Effect ажиллав — count: 0"
// "Cleanup ажиллав — count: 0"   ← count 1 болоход, өмнөх effect цэвэрлэгдэнэ
// "Effect ажиллав — count: 1"

Cleanup функц нь шинэ effect ажиллахаас өмнө ажилладаг тул өмнөх effect-н эхлүүлсэн зүйлсийг цэвэрлэх боломж олгодог.

Cleanup шаардлагагүй тохиолдол

Зарим effect-д cleanup огт хэрэггүй — тэдгээрийг онцлон мэдрэх нь чухал:

jsx
useEffect(() => {
  // ✅ Cleanup хэрэггүй — document.title-г устгах шаардлагагүй
  document.title = "Шинэ гарчиг";
}, [title]);

useEffect(() => {
  // ✅ Cleanup хэрэггүй — localStorage нь persist хэвээр байж болно
  localStorage.setItem("theme", theme);
}, [theme]);

useEffect(() => {
  // ✅ Cleanup хэрэггүй — analytics event нэг удаа илгээгдсэн хангалттай
  trackPageView("/home");
}, []);

Cleanup нь дараах үед шаардлагатай: setInterval/setTimeout, addEventListener, WebSocket/subscription, abort controller.

Дараагийн хичээлд:

useRef hook ашиглан DOM элементийг шууд барьж авах, рендер хийхгүйгээр утга хадгалах аргыг судална. Input-г focus хийх, animation удирдах зэрэг практик жишээгээр ойлгоно.