React / useRef hook

useRef hook

useState өгөгдөл хадгалахад ашиглагдана — гэхдээ state өөрчлөгдөх бүрт component дахин рендер болдог. Заримдаа рендер хийлгүйгээр утга хадгалах, эсвэл DOM элементэд шууд хандах хэрэгтэй болдог. Энэ хоёр хэрэгцээг useRef хангадаг. Нэрэнд нь "ref" буюу reference буюу лавлагаа гэдэг утга агуулагддаг.

useRef үндэс

useRef нь { current: утга } хэлбэртэй объект буцаадаг. current шинж чанарыг унших ба бичих боломжтой — ялгаатай нь энэ нь рендер өдөөдөггүй:

jsx
import { useRef } from "react";

function RefDemo() {
  const countRef = useRef(0);

  function handleClick() {
    // current-г өөрчилнэ — рендер болохгүй
    countRef.current += 1;
    console.log("Дарсан тоо:", countRef.current);
  }

  return (
    <div>
      {/* Энэ тоо дэлгэцэнд шинэчлэгдэхгүй — рендер болдоггүй учраас */}
      <p>Ref утга: {countRef.current}</p>
      <button onClick={handleClick}>Дарах</button>
    </div>
  );
}

countRef.current нэмэгдэж байгаа ч дэлгэцэнд харагдахгүй. Дэлгэц шинэчлэх шаардлагатай бол useState ашигла — зөвхөн рендергүйгээр утга хадгалах шаардлагатай бол useRef.

DOM элементэд хандах

useRef-н хамгийн нийтлэг хэрэглээ бол DOM элементийг шууд барьж авах явдал юм. ref props-г JSX элементэд дамжуулбал current тухайн DOM node болно:

jsx
import { useRef } from "react";

function AutoFocusInput() {
  const inputRef = useRef(null);

  function handleFocus() {
    // DOM элементийн .focus() аргыг шууд дуудна
    inputRef.current.focus();
  }

  function handleClear() {
    inputRef.current.value = "";
    inputRef.current.focus();
  }

  return (
    <div>
      <input ref={inputRef} type="text" placeholder="Энд бичнэ үү..." />
      <button onClick={handleFocus}>Focus хийх</button>
      <button onClick={handleClear}>Цэвэрлэх</button>
    </div>
  );
}

inputRef.current нь яг document.getElementById(...) буцаадаг шиг DOM элементийг буцаана — гэхдээ React-н арга замаар, id хэрэглэхгүйгээр.

useEffect-тэй хослуулах

Component дэлгэцэнд гарсны дараа автоматаар focus хийх нийтлэг хэрэглээ:

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

function SearchBar() {
  const inputRef = useRef(null);

  // Хуудас ачаалагдахад автоматаар focus хийнэ
  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return (
    <div>
      <input ref={inputRef} type="search" placeholder="Хайх..." />
    </div>
  );
}
jsx
import { useRef, useEffect, useState } from "react";

function VideoPlayer({ src, isPlaying }) {
  const videoRef = useRef(null);

  useEffect(() => {
    if (!videoRef.current) return;

    if (isPlaying) {
      videoRef.current.play();
    } else {
      videoRef.current.pause();
    }
  }, [isPlaying]);

  return <video ref={videoRef} src={src} />;
}

Өмнөх утгыг хадгалах

useRef нь render хооронд утгыг хадгалдаг тул өмнөх state-г хадгалахад ашиглаж болно:

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

function usePrevious(value) {
  const prevRef = useRef();

  useEffect(() => {
    prevRef.current = value;
  });

  return prevRef.current;
}

function PriceTracker() {
  const [price, setPrice] = useState(1000);
  const prevPrice = usePrevious(price);

  const change = prevPrice !== undefined ? price - prevPrice : 0;
  const color = change > 0 ? "#4ade80" : change < 0 ? "#fb7185" : "#94a3b8";

  return (
    <div>
      <p>Одоогийн үнэ: {price}₮</p>
      {prevPrice !== undefined && (
        <p style={{ color }}>
          Өмнөх: {prevPrice}₮ ({change > 0 ? "+" : ""}
          {change}₮)
        </p>
      )}
      <button onClick={() => setPrice((p) => p + 50)}>+50₮</button>
      <button onClick={() => setPrice((p) => p - 30)}>-30₮</button>
    </div>
  );
}

Interval ID хадгалах

Interval эсвэл timeout-н ID-г useRef-д хадгалах нь нийтлэг хэлбэр — тэдгээр нь рендер өдөөх шаардлагагүй:

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

function Stopwatch() {
  const [elapsed, setElapsed] = useState(0);
  const [running, setRunning] = useState(false);
  const intervalRef = useRef(null);

  function start() {
    if (running) return;
    setRunning(true);
    intervalRef.current = setInterval(() => {
      setElapsed((prev) => prev + 10);
    }, 10);
  }

  function stop() {
    clearInterval(intervalRef.current);
    setRunning(false);
  }

  function reset() {
    clearInterval(intervalRef.current);
    setRunning(false);
    setElapsed(0);
  }

  const seconds = (elapsed / 1000).toFixed(2);

  return (
    <div>
      <p>{seconds} секунд</p>
      <button onClick={start} disabled={running}>
        Эхлэх
      </button>
      <button onClick={stop} disabled={!running}>
        Зогсоох
      </button>
      <button onClick={reset}>Дахилт</button>
    </div>
  );
}

intervalRef.current = setInterval(...) нь рендер өдөөхгүй тул state ашиглахаас илүү тохиромжтой.

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

useContext hook ашиглан props дамжуулалтгүйгээр component мод даяар өгөгдөл хуваалцах аргыг судална. Theme, хэрэглэгчийн мэдээлэл зэрэг глобал state-г хэрхэн зохицуулах талаар ойлгоно.