useEffect цэвэрлэгээ
Өмнөх хичээлд useEffect-н суурийг тавьж, cleanup функц буцаах аргыг товч үзлээ. Энэ хичээлд cleanup-г гүнзгийрч судална — яагаад заавал хэрэгтэй, хаана орхивол аюултай, бодит хэрэглээний жишээнүүдийг практикаар ойлгоно. Cleanup нь React-н хамгийн анхааралтай байх ёстой сэдвүүдийн нэг юм.
Яагаад cleanup шаардлагатай вэ?
React component дэлгэцнээс гарах (unmount) буюу useEffect дахин ажиллахаас өмнө өмнөх effect-г зогсоох хэрэгтэй. Үгүй бол хуучин code шинэ state-г өөрчлөхийг оролдож, алдаа гаргана:
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 буюу санах ойн алдагдал юм:
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 заавал хэрэгтэй:
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-уудыг ч мөн адил цэвэрлэнэ:
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 хэзээ ажилладаг вэ?
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 огт хэрэггүй — тэдгээрийг онцлон мэдрэх нь чухал:
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 удирдах зэрэг практик жишээгээр ойлгоно.