✅동일한 계산을 반복해서 수행하지 않고, 이미 계산한 결과를 메모리에 저장했다가 재사용하여 프로그램의 성능을 향상시키는 기술
function hundred () {
return 100
}
위와 같이 같은 값을 리턴하는 함수를 여러 번 호출하는 경우가 있다고 하자. 메모이제이션 기법은 처음 계산할 때 그 값을 메모리에 저장해두고 그 값을 불러온다. 이를 통해 불필요한 계산을 방지할 수 있다.
먼저 기억해야할 것이 있다.
컴포넌트안에 복잡한 연산을 하는 함수를 직접 호출한다면 리렌더링이 발생할 때마다 이 복잡한 함수가 재호출된다. 만약 리렌더링이 빈번하게 발생한다면 이것이 성능 저하의 원인이 될 수 있다.
이때 사용할 수 있는 것이 useMemo 훅이다.
useMemo 사용법
useMemo(()=>{
// 복잡한 함수
}, [])
useMemo는 첫 번째 인자로 콜백함수를, 두 번째 인자로 의존성 배열을 받는다. 의존성 배열에 명시한 값이 변하지 않는다면 메모이제이션을 하여 불필요한 계산을 방지하고 성능을 최적화할 수 있다.
useMemo는 계산 비용이 높은 연산에 사용하라고 되어있으나, 일반적인 React 어플리케이션에서 시간이 많이 소요되는 계산은 드물다. 그럼 어떨 때 사용할 수 있을까?
다음은 예제는 각각 입력한 값을 대문자와 소문자로 변환해주고, 변환한 값을 화면에 렌더링해준다.
import React, { useState, useMemo } from "react";
function changeUpperCase(text) {
console.log("대문자 변환");
const upperCaseText = text.toUpperCase();
return upperCaseText;
}
function changeLowerCase(text) {
console.log("소문자 변환");
const lowerCaseText = text.toLowerCase();
return lowerCaseText;
}
function App() {
const [lowerCaseText, setLowerCaseText] = useState("");
const [upperCaseText, setUpperCaseText] = useState("");
const upperCaseResult = changeUpperCase(lowerCaseText);
const lowerCaseResult = changeLowerCase(upperCaseText);
return (
<div>
<input
type="text"
value={lowerCaseText}
onChange={(e) => setLowerCaseText(e.target.value)}
/>
<p>대문자 변환: {upperCaseResult}</p>
<input
type="text"
value={upperCaseText}
onChange={(e) => setUpperCaseText(e.target.value)}
/>
<p>소문자 변환: {lowerCaseResult}</p>
</div>
);
}
export default App;
위의 코드에서 input에 값을 입력하면 state가 업데이트 되면서 App 컴포넌트가 리렌더링된다. 함수 내부의 changeUpperCase와 lowerCaseResult는 컴포넌트가 리렌더링이될 때마다 호출된다.

다음과 같이 useMemo를 사용하면 의존성 배열로 지정한 값이 변할 때만 실행되는 것을 확인 할 수 있다.
const upperCaseResult = useMemo(() => {
return changeUpperCase(lowerCaseText);
}, [lowerCaseText]);
const lowerCaseResult = useMemo(() => {
return changeLowerCase(upperCaseText);
}, [upperCaseText]);

import React, { useState, useEffect, useMemo } from "react";
import css from "./App.css";
function App() {
const [like, setLike] = useState(0);
const [isDog, setIsDog] = useState(true);
const pet = {
type: isDog ? "🐶" : "🐱",
};
useEffect(() => {
console.log("귀여운 게 최고야!");
}, [pet]);
return (
<div>
<h2>댕댕이 vs 냥냥이</h2>
<button onClick={() => setIsDog(!isDog)}>클릭</button>
<p>{pet.type}</p>
<h2>좋아요</h2>
<input
type="number"
value={like}
onChange={(e) => setLike(e.target.value)}
/>
</div>
);
}
export default App;
위의 코드에서 useEffect는 pet이라는 객체를 의존성 배열로 가지고 있다. 이때 좋아요를 변경하면 콘솔로그가 찍힐까? 정답은 콘솔로그가 찍힌다.

이는 참조타입의 특성 때문이다. like라는 state가 변하면 App 컴포넌트는 리렌더링이 되는데, 이때 객체는 이전과는 다른 메모리상의 공간에 저장이 되면서 pet이라는 변수는 새로운 객체의 주소값을 참조하게 된다. 따라서 useEffect의 의존성배열인 pet이 변경되었다고 인식하고 콘솔로그가 찍히게 되는 것이다.
다음과 같이 useMemo를 사용하면 like의 값이 변해도 콘솔로그가 찍히지 않는다.
const pet = useMemo(() => {
return {
type: isDog ? "🐶" : "🐱",
};
}, [isDog]);

메모이제이션은 메모리 사용량을 증가시킬 수 있다. 따라서 계산량이 많은 작업을 효율적으로 처리하고, 렌더링 성능을 최적화할 수 있을 경우에만 제한적으로 사용하는 것이 바람직하다.