React / useEffect hook

useEffect hook

Component дэлгэцэнд гарсны дараа юм хийх шаардлага байнга гардаг — API-аас өгөгдөл татах, хуудасны title өөрчлөх, цаг тоолуур эхлүүлэх, хөтчийн event-г сонсох гэх мэт. Эдгээр нь side effect буюу рендерийн гадуурх үйлдлүүд юм. useEffect hook нь эдгээр side effect-уудыг зохицуулдаг React-н чухал хэрэгсэл юм.

useEffect үндэс

useEffect нь хоёр аргумент авдаг: ажиллуулах функц, хамааралт (dependency) массив:

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

function PageTitle({ title }) {
  useEffect(() => {
    // Component рендер болсны дараа ажиллана
    document.title = title;
  });

  return <h1>{title}</h1>;
}

Dependency массивгүй бол useEffect рендер хийх болгонд ажиллана. Энэ ихэнх тохиолдолд хэт олон удаа ажиллах тул dependency массив нэмэх нь зөв.

Dependency массив

Dependency массив нь useEffect хэзээ ажиллахыг тодорхойлдог:

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

function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("");

  // 1. Зөвхөн нэг удаа — component эхлэх үед (mount)
  useEffect(() => {
    console.log("Component эхэллээ");
  }, []);

  // 2. count өөрчлөгдөх бүрт
  useEffect(() => {
    console.log("count өөрчлөгдлөө:", count);
  }, [count]);

  // 3. count эсвэл name өөрчлөгдөх бүрт
  useEffect(() => {
    console.log("count эсвэл name өөрчлөгдлөө");
  }, [count, name]);

  return (
    <div>
      <button onClick={() => setCount((c) => c + 1)}>+1</button>
      <input value={name} onChange={(e) => setName(e.target.value)} />
    </div>
  );
}

[] хоосон массив нь "нэг л удаа ажилла, өөрчлөлтийг хяр" гэсэн утгатай. [count] гэвэл count өөрчлөгдөх бүрт ажиллана.

API-аас өгөгдөл татах

useEffect-н хамгийн нийтлэг хэрэглээ бол өгөгдөл татах явдал юм:

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

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchUser() {
      try {
        setIsLoading(true);
        const response = await fetch(`https://api.example.com/users/${userId}`);

        if (!response.ok) {
          throw new Error("Өгөгдөл татаж чадсангүй");
        }

        const data = await response.json();
        setUser(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setIsLoading(false);
      }
    }

    fetchUser();
  }, [userId]); // userId өөрчлөгдөх бүрт дахин татна

  if (isLoading) return <p>Ачааллаж байна...</p>;
  if (error) return <p>Алдаа: {error}</p>;
  if (!user) return null;

  return (
    <div>
      <h2>{user.username}</h2>
      <p>{user.xp} XP</p>
    </div>
  );
}

useEffect дотор async/await шууд бичиж болохгүй — тул дотор нь async функц тодорхойлж дуудна.

Cleanup функц

Зарим effect зогсоох хэрэгтэй — цаг тоолуур, event listener зэрэг. useEffect-аас функц буцааснаар cleanup хийнэ:

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

function LiveClock() {
  const [time, setTime] = useState(new Date());

  useEffect(() => {
    // Interval эхлүүлнэ
    const interval = setInterval(() => {
      setTime(new Date());
    }, 1000);

    // Cleanup — component устах үед interval зогсооно
    return () => {
      clearInterval(interval);
    };
  }, []); // Нэг удаа л эхлүүлнэ

  return <p>{time.toLocaleTimeString("mn-MN")}</p>;
}
jsx
function WindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    function handleResize() {
      setWidth(window.innerWidth);
    }

    window.addEventListener("resize", handleResize);

    // Cleanup — event listener-г устгана
    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, []);

  return <p>Цонхны өргөн: {width}px</p>;
}

Cleanup нь component дэлгэцнээс гарах (unmount) үед ажиллана. Цэвэрлэгээгүй бол memory leak буюу санах ойн алдагдал үүсэж болно.

Хуудасны title шинэчлэх

jsx
import { useEffect } from "react";

function LessonPage({ lessonTitle }) {
  useEffect(() => {
    document.title = `${lessonTitle} — ulaanbaatar.app`;

    // Хуудаснаас гарах үед анхны title-г сэргээнэ
    return () => {
      document.title = "ulaanbaatar.app";
    };
  }, [lessonTitle]);

  return <h1>{lessonTitle}</h1>;
}

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

useEffect cleanup-г гүнзгийрч судална. Хаана cleanup шаардлагатай, хаана шаардлагагүй, алдаанаас хэрхэн зайлсхийх аргуудыг практик жишээгээр ойлгоно.