useCallback hook
Өмнөх хичээлд useMemo ашиглан утга-ыг санаж үлдэхийг сурсан. useCallback нь ижил зарчмаар ажилладаг — гэхдээ утгын оронд функц-ийг санаж үлдэдэг. React.memo-тэй хослуулан хэрэглэхэд маш чухал!
Яагаад useCallback хэрэгтэй вэ?
JavaScript-д функц бол object. Тиймээс component дахин рендерлэгдэх болгонд дотроо байгаа функцүүд шинэ instance-аар үүсдэг — агуулга яг адилхан ч гэсэн.
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 ашиглах нь
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-д нэмэх хэрэгтэй:
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
Хоёрын ялгааг ойлгоход чухал:
// 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-тай холбон ойлгоно.