React / Component амьдралын мөчлөг

Component амьдралын мөчлөг

React-н component бол "амьд" зүйл шиг — төрж, өсч, үхдэг. Энэ үйл явцыг lifecycle (амьдралын мөчлөг) гэдэг. Lifecycle-г ойлгосноор useEffect-ийг илүү ухаалгаар хэрэглэж, олон нийтлэг алдаанаас зайлсхийж чадна.

Lifecycle-н гурван үе шат

React component-н амьдралын мөчлөг гурван үндсэн үе шаттай:

1. Mount — component DOM-д нэмэгдэх үе. Зөвхөн нэг удаа болдог.

2. Update — props эсвэл state өөрчлөгдөх болгон component дахин рендерлэгдэх үе. Олон удаа болж болно.

3. Unmount — component DOM-оос хасагдах үе. Зөвхөн нэг удаа болдог.

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

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

  useEffect(() => {
    // 🟢 Mount — component DOM-д гарсны дараа ажилладаг
    console.log("Timer mount боллоо");
    const id = setInterval(() => {
      setSeconds((s) => s + 1); // 🔄 Update — seconds өөрчлөгдөнө
    }, 1000);

    return () => {
      // 🔴 Unmount — component DOM-оос хасагдахын өмнө ажилладаг
      console.log("Timer unmount боллоо");
      clearInterval(id); // interval-ийг цэвэрлэх шаардлагатай!
    };
  }, []); // хоосон array = зөвхөн mount/unmount-д ажилладаг

  return <p>Хугацаа: {seconds} секунд</p>;
}

export default function App() {
  const [show, setShow] = useState(true);

  return (
    <div>
      <button onClick={() => setShow(!show)}>
        {show ? "Нуух" : "Харуулах"}
      </button>
      {show && <Timer />}
    </div>
  );
}

"Нуух" товч дарахад Timer unmount болж interval зогсоно. "Харуулах" дарахад дахин mount болж шинэ interval эхэлнэ.

useEffect ба dependency array

useEffect-н хоёр дахь аргумент болох dependency array нь lifecycle-г хянадаг:

jsx
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [status, setStatus] = useState("Онлайн");

  // 1️⃣ Зөвхөн mount-д ажиллана (хоосон array)
  useEffect(() => {
    console.log("Компонент гарлаа!");
    return () => console.log("Компонент явлаа!");
  }, []);

  // 2️⃣ Mount + userId өөрчлөгдөх болгонд ажиллана
  useEffect(() => {
    async function fetchUser() {
      const res = await fetch(`/api/users/${userId}`);
      const data = await res.json();
      setUser(data);
    }
    fetchUser();
  }, [userId]);

  // 3️⃣ Рендерлэгдэх болгонд ажиллана (array байхгүй)
  useEffect(() => {
    document.title = `${status}${user?.name ?? "..."}`;
  });

  return (
    <div>
      <p>{user?.name}</p>
      <p>Төлөв: {status}</p>
    </div>
  );
}

Нийтлэг хэрэглээнүүд

Lifecycle pattern-уудыг практик жишээгээр харцгаая:

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

function SearchBox() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (!query.trim()) {
      setResults([]);
      return;
    }

    setLoading(true);

    // Хэрэглэгч бичихийн дагуу хайлт хийхгүй — 500ms хүлээнэ (debounce)
    const timer = setTimeout(async () => {
      try {
        const res = await fetch(`/api/search?q=${query}`);
        const data = await res.json();
        setResults(data);
      } finally {
        setLoading(false);
      }
    }, 500);

    // Cleanup: дараагийн effect ажиллахын өмнө timer-ийг цэвэрлэнэ
    return () => {
      clearTimeout(timer);
      setLoading(false);
    };
  }, [query]);

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Хайх..."
      />
      {loading && <p>Хайж байна...</p>}
      <ul>
        {results.map((r) => (
          <li key={r.id}>{r.title}</li>
        ))}
      </ul>
    </div>
  );
}

Cleanup функц маш чухал — component unmount болоход гүйж байгаа async ажлуудыг зогсоохгүй бол memory leak үүсдэг. Cleanup нь энэ асуудлаас сэргийлдэг.

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

Portal гэж юу болохыг сурна — component-н DOM бүтцийн гадна элемент рендерлэх арга. Modal, tooltip, dropdown зэрэгт яагаад Portal ашигладгийг ойлгоно.