React / useCallback hook

useCallback hook

Өмнөх хичээлд useMemo ашиглан утга-ыг санаж үлдэхийг сурсан. useCallback нь ижил зарчмаар ажилладаг — гэхдээ утгын оронд функц-ийг санаж үлдэдэг. React.memo-тэй хослуулан хэрэглэхэд маш чухал!

Яагаад useCallback хэрэгтэй вэ?

JavaScript-д функц бол object. Тиймээс component дахин рендерлэгдэх болгонд дотроо байгаа функцүүд шинэ instance-аар үүсдэг — агуулга яг адилхан ч гэсэн.

jsx
function Parent() {
  const [count, setCount] = useState(0);

  // count өөрчлөгдөх бүрт handleClick шинэ функц болно
  const handleClick = () => {
    console.log("Дарлаа!");
  };

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>+1</button>
      {/* handleClick өөрчлөгдсөн гэж React үзэж Child дахин рендерлэнэ */}
      <Child onClick={handleClick} />
    </div>
  );
}

const Child = memo(function Child({ onClick }) {
  console.log("Child рендерлэгдлээ");
  return <button onClick={onClick}>Дарах</button>;
});

React.memo ашигласан ч handleClick дахин рендерлэгдэх болгонд шинэ функц болдог учир Child хамгаалалт ажиллахгүй болно.

useCallback ашиглах нь

jsx
import { useState, useCallback, memo } from "react";

function Parent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("");

  // dependency өөрчлөгдөхгүй бол яг нэг л функцийг санаж үлдэнэ
  const handleClick = useCallback(() => {
    console.log("Дарлаа!");
  }, []); // хоосон array — хэзээ ч шинэ функц үүсгэхгүй

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>+1 ({count})</button>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      {/* text эсвэл count өөрчлөгдөхөд Child дахин рендерлэгдэхгүй */}
      <Child onClick={handleClick} />
    </div>
  );
}

const Child = memo(function Child({ onClick }) {
  console.log("Child рендерлэгдлээ");
  return <button onClick={onClick}>Дарах</button>;
});

Одоо count эсвэл text өөрчлөгдөхөд handleClick яг нэгэн функц хэвээр байдаг тул Child дахин рендерлэгдэхгүй.

Dependency-тэй useCallback

Хэрэв функц дотроо state эсвэл props ашигладаг бол тэдгээрийг dependency array-д нэмэх хэрэгтэй:

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

function TodoList() {
  const [todos, setTodos] = useState([]);
  const [filter, setFilter] = useState("all");

  // filter өөрчлөгдөхөд л шинэ функц үүснэ
  const addTodo = useCallback(
    (text) => {
      setTodos((prev) => [...prev, { id: Date.now(), text, done: false }]);
    },
    [], // setTodos тогтмол тул dependency-д нэмэхгүй
  );

  // filter өөрчлөгдөхөд шинэ функц үүсгэх шаардлагатай
  const getFiltered = useCallback(() => {
    if (filter === "done") return todos.filter((t) => t.done);
    if (filter === "active") return todos.filter((t) => !t.done);
    return todos;
  }, [todos, filter]);

  return (
    <div>
      <AddForm onAdd={addTodo} />
      <select value={filter} onChange={(e) => setFilter(e.target.value)}>
        <option value="all">Бүгд</option>
        <option value="active">Идэвхтэй</option>
        <option value="done">Дууссан</option>
      </select>
      <ul>
        {getFiltered().map((t) => (
          <li key={t.id}>{t.text}</li>
        ))}
      </ul>
    </div>
  );
}

useMemo vs useCallback

Хоёрын ялгааг ойлгоход чухал:

jsx
// useMemo — утга буцаана
const sortedList = useMemo(() => {
  return [...items].sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
// sortedList нь array

// useCallback — функц буцаана
const handleSort = useCallback(() => {
  setItems((prev) => [...prev].sort((a, b) => a.name.localeCompare(b.name)));
}, []);
// handleSort нь функц

useCallback(fn, deps) нь useMemo(() => fn, deps)-тэй яг адил утгатай. Гэхдээ функц буцаах тохиолдолд useCallback илүү цэвэрхэн харагддаг.

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

Component-ийн амьдралын мөчлөг (lifecycle) буюу component хэрхэн mount, update, unmount болдгийг сурна. useEffect-ийг lifecycle-тай холбон ойлгоно.