useCallback() 은 리액트에서 내놓은 여러가지 hooks 중 하나이다.
함수를 메모제이션 (Memoization) 하기위해 사용된다.
메모제이션 (Memoizatio) 이란?
- 함수의 반환 값을 캐시하여 동일한 인자가 함수에 전달될 때 이전에 계산한 값을 반환함으로써
계산 속도를 향상시키는 것을 말한다.- 일반적으로 함수는 동일한 인자에 대해서는 동일한 결과를 반환하는데, 특정인자를 가지고
계산을 한 결과를 저장하고, 해당인자가 다시 전달될때, 저장해두었던 결과값을 반환하여
해당 계산을 생략하는것이다.- 함수 실행속도를 개선하여 성능최적화에 사용된다.
다시말해, useCallback() 은 받아온 인자가 변경되기 전까지는 그 전에 실행되었던
데이터를 저장해둘 수 있는 기능을 한다는것이다.
useCallback() 은 두가지 인자를 받는데,
첫번째는 함수를 받고
두번째는 배열을 받는다.
두번째로 들어오는 배열을 이용해서 첫번째 인자인 함수가 실행되게끔 하는것이다.
const example = useCallback(함수, 배열); <- 이런 느낌이겠지,
그렇다면 useCallback() 은 어떻게 사용하는걸까? 예를 하나 들어보자.
const example2 = (x, y) => {
x + y
}
만약 이런 함수가 있다면, 이 example2 함수는 렌더링이 될때마다 계속 새롭게 만들어질것이다.
하지만 이 함수에 useCallback() 을 씌운다면?
const example3 = useCallback(() => {
x + y
},[x, y])
x, y 의 값이 변하지 않는이상 한번 반환했던 함수를 그대로 반환하게 되는것이다.
x, y 의 값이 변한다면, 해당 결과값을 다시 만들어 반환하고 또 그 데이터를 저장한다.
사실 다들 알다시피, 컴퓨터의 수행능력은 매우 빨라서 컴포넌트가 렌더링 될때마다
함수를 새로 선언하건, 저장해두었던 함수를 다시 내보내건, 그렇게 차이가 크게 느껴지진 않을것이다.
그렇다면 왜 useCallback() 을 쓰는것일까?
이 이유를 알기위해선 먼저 알아둬야할것이 있다. 바로 자바스크립 함수의 동등성이다.
콘솔을 열어서 이름만 다르고 같은 기능을 하는 함수를 비교해본다면
> const add1 = () => x + y;
undefined
> const add2 = () => x + y;
undefined
> add1 === add2
false
다르다는 결과가 나온다, 이유는 자바스크립트는 함수도 객체로 취급을 하기때문에 메모리 주소에 의한
참조비교가 일어나기때문이다. 다시말해, 같은 함수가 여러번 호출이 되면 그것은 하나의 함수로
인식하는것이 아니라 각각이 다른 메모리에 저장이 된다는 뜻이다. 함수를 10번 리렌더링 하게 되면
메모리에 같은 함수의 각각 다른 주소가 10개가 쌓인다는 말이 된다.
이렇게 된다면, 만약 해당 함수가 인자의 개념으로 다른 함수에 넘어가다가 예기치 못한 성능문제로
이어질 수 가 있다. 렌더링 할때마다 계속 다른 주소의 같은 함수가 넘어오니깐 말이다.
해당 문제를 이해해보자.
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]);
}
해당 게시물을 참조한 글에서 가져온 코드이다.
useEffec() 를 이용하여 이 문제를 이해해보자.
이 useEffect() 는 fetchUser 라는 함수가 변경될때만 호출된다.
하지만 여기서 문제가 발생한다, fetchUser 는 뭔가 userId 의 변경에 의해서
함수가 변경되니, useEffect() 는 fetchUser 의 userId 가 변경되면 호출되겠구나
라고 생각할 수 있지만, 그렇지 않다. 위에서 말했듯이 같은 함수여도, 같은 인자를 받더라도
리렌더링을 하게 되면 다른 메모리에 다른 주소로 저장되기 때문에 userId 의 변화에 상관없이
렌더링이 일어날때마다 useEffect() 가 실행되는것이다. 아주 불필요한 동작이다.
이때 useCallback() 을 사용한다.
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]);
이렇게 fetchUser 함수를 useCallback() 으로 감싸게 되면 컴포넌트가 리렌더링 되더라도
동일한 참조값을 가지므로 이때야 비로소 userId 가 변경될때까진 재호출 되지 않게 된다.
이런 이유로 useCallback() 을 사용하게 된다. 참고하길 바란다.