함수를 메모이제이션하기 위해서 사용되는 hook이다.
의존값이 바뀔때는 함수를 생성하고, 값이 바뀌지 않으면 함수를 생성하지 않고 이전에 지정해둔 함수를 사용한다.
따라서, 랜더링 시 불필요한 함수 생성을 막아므로 성능을 최적화할 수 있다.
첫번째 인자로 넘어온 함수를, 두번째 인자로 넘어온 배열 형태의 함수 실행 조건의 값이 변경될때까지 저장해놓고 재사용할 수 있게 한다.
const memoizedCallback = useCallback(함수, 배열);
⭐️그렇다면 useCallback()은 어떻게 사용해야 의미있는 성능 향상을 기대할 수 있을까?
JS에서는 함수 또한 객체로 취급하기 때문에 메모리 주소에 의한 참조 비교가 일어난다. 따라서 리액트 컴포넌트 함수 내에서 어떤 함수를 다른 함수의 인자로 넘기거나 자식 컴포넌트의 prop으로 넘길 때 예상치 못한 성능 문제가 발생할 수 있다.
> const add1 = () => x + y;
undefined
> const add2 = () => x + y;
undefined
> add1 === add2
false
React hook은 불필요한 작업을 줄이기 위해서 두번째 인자에 첫번째 함수가 의존해야할 배열을 받는다.
컴포넌트에서 API를 호출하는 코드는 fetchUser 함수가 변경될 때만 호출된다. 이때 함수의 동등성을 판단하는 방식 때문에 예상치 못한 무한 루프에 발생.
fetchUser는 함수이기 때문에, userId 값이 바뀌든 말든 컴포넌트가 랜더링될 때 마다 새로운 참조값으로 변경. 그러면 useEffect() 함수가 호출되어 user 상태값이 바뀌고 그러면 다시 랜더링이 되고 그럼 또 다시 useEffect() 함수가 호출되는 악순환이 반복.
import React, { useState, useEffect } from "react";
function Profile({ userId }) {
const [user, setUser] = useState(null);
const fetchUser = () =>
fetch(`https://your-api.com/users/${userId}`)
.then((response) => response.json())
.then(({ user }) => user);
useEffect(() => {
fetchUser().then((user) => setUser(user));
}, [fetchUser]);
// ...
}
그렇다면 fetchUser함수의 랜더링을 막으면 될 것이다. useCallback을 이용하여 컴포넌트가 재런더링되어도 fetchUser함수의 참조값을 동일하게 유지하는 것이다. 그러면 useEffect()에 넘어온 함수는 userId 값의 변경 없이는 재호출되지 않는다.
import React, { useState, useEffect } from "react";
function Profile({ userId }) {
const [user, setUser] = useState(null);
const fetchUser = useCallback(
() =>
fetch(`https://your-api.com/users/${userId}`)
.then((response) => response.json())
.then(({ user }) => user),
[userId]
);
useEffect(() => {
fetchUser().then((user) => setUser(user));
}, [fetchUser]);
// ...
}
useCallback() hook 함수는 자식 컴포넌트의 랜더링의 불필요한 랜더링을 줄이기 위해서 React.memo() 함수와도 사용가능.
props값이 변경없이는 재호출이 일어나지 않도록 React.memo로 감싸주지만, 앞서 말했듯이 컴포넌트의 재랜더링으로 함수의 참조값이 변경되어 버린다. 따라서, useCallback을 통해 두번째 인자로 각 함수가 의존하고 있는 상태를 배열로 넘겨서 각각 함수의 실행에 다른 함수가 영향을 받지 않게 한다.
예시 : https://www.daleseo.com/react-hooks-use-callback/
💡 잠깐) React.memo?
: https://velog.io/@pjh1011409/React-Hooks-React.memo
💡 중요)
쉽게 말해, 컴포넌트의 리랜더링 시 함수는 새롭게 만들어서 호출된다. 즉, 새롭게 만들어진 함수는 기존의 함수와 같은 함수가 아니다. 서로 메모리 주소가 다르기 때문이다. 그래서, 의존 배열로 함수를 넘길때에도 재랜더링시 생성되는 함수가 useEffect의 의존성배열로 들어가는 값이 달랐기 때문에 무한루프가 도는 것이다.
따라서, **useCallback을 통해 기존의 함수 자체를 저장(메모리 주소 값을 저장)해놓고 다시 사용한다. 따라서, 다른 함수의 인자로 넘기거나 자식 컴포넌트의 prop로 전달할 때 예상치 못한 성능저하를 막을 수 있는 것이다.
⭐️ React.memo, useMemo, useCallback은 모두 불필요한 랜더링 또는 연산을 제어하여 성능 최적화를 목적으로 한다.
💡 잠깐)
React.memo는 HOC이고, useMemo와 useCallback은 hook이다.
Reeact.memo는 HOC이기 때문에 클래스형 컴포넌트, 함수형 컴포넌트 모두 사용 가능하지만, useMemo는 hook이기 때문에 함수형 컴포넌트 안에서만 사용 가능하다
⭐️ useMemo는 메모이제이션된 '값'을 반환한다.
useMemo(() => fn, deps)
⭐️ useCallback는 메모이제이션된 '함수'을 반환한다.
useCallback(fn, deps)
⭐️ useMemo는 함수의 연산량이 많을 때 이전 결과값을 재사용하는 목적이고, useCallback은 함수가 재생성되는 것을 방지하기 위한 목적을 가진다.
참조
- https://www.daleseo.com/react-hooks-use-callback/
- https://gisastudy.tistory.com/92
- https://velog.io/@sangbin2/useCallback
- https://velog.io/@sunkim/React.memo-useMemo-useCallback-%EC%97%AD%ED%95%A0-%EB%B0%8F-%EC%B0%A8%EC%9D%B4%EC%A0%90
참고하기 좋은 사이트 : https://basemenks.tistory.com/238