useMemo와 useCallback를 살펴보기 전에, memoization
에 대해 먼저 알아보자
기존에 수행한 연산의 결과값을 어딘가에 저장해두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법.
📍 적절히 사용하면 중복 연산을 피할 수 있어서 애플리케이션의 성능을 최적화 할 수 있다!
function Sarah(props) {
// ....... 로직
return;
}
위와 같은 컴포넌트 함수는 React에서 랜더링이 일어날 때마다 호출이 된다.
만약, 함수가 내부적으로 복잡한 연산을 수행한다면 랜더링 될 때마다 지연이 발생할 수 있다. 이러한 문제를 해결하기 위해 memoization
을 적용 시켜 줄 수 있다.
특정 값(value)를 재사용하고자 할 때 사용한다.
아래 예시를 보면, props로 넘어온 value
값을 Calculator
라는 함수에 인자로 넘겨서 result
값을 구한 후, <div>
엘리먼트로 출력을 하고 있다.
만약 여기서 Calculator
가 내부적으로 복잡한 연산을 하는 함수라고 가정해보자.
해당 컴포넌트는 렌더링을 할 때마다 이 함수를 계속 호출할 것이고, 그 때마다 시간이 몇 초 이상 소요가 될 것이다. 결국 이 지연은 렌더링에도 영향을 미치게 될 것이며, 사용자는 앱의 로딩 속도가 느리다는 생각을 하게 될 것이다.
function Calculator({value}){
const result = calculate(value);
return <>
<div>
{result}
</div>
</>;
}
하지만 아래와 같이 useMemo를 사용함으로써, value
의 값을 저장 해뒀다가 필요할 때 사용함으로써 Calculator
함수호출을 줄일 수 있다. 특히 value
값이 렌더링 할 때마다 계속 바뀌지 않는 경우라면 더욱 좋다.
import { useMemo } from "react";
function Calculator({value}){
const result = useMemo(() => calculate(value), [value]);
return <>
<div>
{result}
</div>
</>;
}
이런식으로 useMemo
를 호출하여 Calculator
를 감싸주면, 이전에 구축된 렌더링과 새로 구축되는 렌더링을 비교해 value
값이 동일할 경우에는 이전 렌더링의 value
값을 그대로 재활용 할 수 있게 된다.
특정 결과값을 재사용하는 useMemo와 달리 useCallback은 함수의 재사용을 위해 사용한다.
아래 코드를 보면, Calculator
컴포넌트 내에 add
라는 함수가 선언되어 있다. 이 add
함수는 props로 넘어온 x
와 y
의 값을 더해 <div>
태그에 값을 출력하고 있다.
function Calculator({x, y}){
const add = () => x + y;
return <>
<div>
{add()}
</div>
</>;
}
useMemo
와 마찬가지로 해당 컴포넌트가 리렌더링 되더라도 그 함수가 의존하고 있는 값인 x
와 y
가 바뀌지 않을 때, useCallback
Hook을 사용할 수 있다. 즉. x
와 y
값이 동일하다면 다음 렌더링 때 이 함수를 다시 사용하는 것이다.
import React, { useCallback } from "react";
function Calculator({x, y}){
const add = useCallback(() => x + y, [x, y]);
return <>
<div>
{add()}
</div>
</>;
}
useCallback
은 참조 동등성에 의존한다. React는 JavaScript 언어로 만들어진 오픈소스 라이브러리이기 때문에 기본적으로 JavaScript의 문법을 따라간다. JavaScript에서 함수는 객체이다. 객체는 메모리에 저장할 때 값을 저장하는 게 아니라 값의 주소를 저장하기 때문에, 반환하는 값이 같을 지라도 일치연산자로 비교했을 때 false
가 출력된다.
function doubleFactory(){
return (a) => 2 * a;
}
const double1 = doubleFactory();
const double2 = doubleFactory();
double1(8); // 16
double2(8); // 16
double1 === double2; // false
double1 === double1; // true
double1
과 double2
는 같은 함수를 할당 했음에도 메모리 주소 값이 다르기 때문에 같다고 보지 않는다. 두개의 함수는 동일한 코드를 공유하더라도 메모리 주소가 다르기 때문에, 메모리 주소에 의한 참조 비교 시 다른 함수로 본다.
이는 React 또한 같다. React는 리렌더링 시 함수를 새로 만들어서 호출을 한다. 새로 만들어 호출된 함수는 기존의 함수와 같은 함수가 아니다. 그러나 useCallback
을 이용해 함수 자체를 저장해서 다시 사용하면 함수의 메모리 주소 값을 저장했다가 다시 사용한다는 것과 같다고 볼 수 있다. 따라서 React 컴포넌트 함수 내에서 다른 함수의 인자로 넘기거나 자식 컴포넌트의 prop으로 넘길 때 예상치 못한 성능 문제를 막을 수 있다.