[React] 28. useMemo와 연산 최적화

서회정's avatar
Mar 06, 2026
[React] 28. useMemo와 연산 최적화

 

1. useMemo란

 
💡
메모이제이션 기법을 기반으로 불 필요한 연산을 최적화하는 리액트 훅이다
 
notion image
 

✅ 메모이제이션 기법이란?

 
💡
Memoization : 기억해두기, 메모해두기 라는 뜻
 
notion image
 
앱에서 반복적으로 수행되는 동일한 연산이
매 번 새롭게 연산 될 수 있다.
 
notion image
 
메모이제이션 기법은 반복적으로 수행되는
동일한 연산이 있을 때 최초에 실행된 연산의 결과값을
메모리에 보관했다가 다음에 이 연산이 필요할 때
메모리에 저장된 결과 값을 바로 돌려주게 한다.
 
notion image
 

2. 실습

 
저번 시간에 만들어두었던 투두 리스트 앱을 활용해보자.
 

1. 연산 로직 추가

 
연산 최적화 실습을 위해 기존에 있던 앱에 기능을 추가해보자.
List컴포넌트에서 총 투두 갯수와 완료된 투두, 미완료된 투두를
화면으로 보여주는 로직을 만들자.
 
먼저 해당하는 기능을 수행할 함수를 만드는데,
각각의 갯수를 구하는 연산을 진행하고,
객체로 한 번에 반환하자.
 
const getAnalyzedData = () => { const totalCount = todos.length; const completeCount = todos.filter((todo) => todo.isComplete).length; const notCompleteCount = totalCount - completeCount; return { totalCount, completeCount, notCompleteCount, }; };
 
구조분해할당으로 객체에 담긴 값을 순서대로 받아와주자.
 
const { totalCount, completeCount, notCompleteCount } = getAnalyzedData();
 
화면에 그대로 렌더링해주자.
 
<div> <p>전체: {totalCount}</p> <p>완료: {completeCount}</p> <p>미완료: {notCompleteCount}</p> </div>;
notion image
 

2. 불필요한 연산 확인

 
추가한 기능에서 완료된 투두의 갯수를 연산하는 식은
투두가 추가될 때마다 점점 더 연산이 오래 걸리는 코드가 될 것이다.
filter 메서드 자체가 배열을 모두 순회하기 때문이다.
 
그래서 가능한 이 함수가 불필요하게 호출되는 경우를 방지해야한다.
 
notion image
 
현재는 컴포넌트 내에서 바로 호출되고 있기 때문에
List컴포넌트가 리랜더링 될 때마다 불필요하게 연산이 된다.
 
notion image
 
해당하는 함수가 호출될 때 콘솔에 출력이 되는 코드를
작성해보면 눈으로 확인할 수 있다.
 
notion image
 
List컴포넌트 내에 검색창에 입력만 해도 해당 함수가
입력값이 바뀔 때마다 호출되어 불필요한 연산이 발생함을 알 수 있다.
 
notion image
 
 

3. useMemo 사용해보기

 
불필요한 연산을 막기 위해 useMemo 훅을 사용해보자.
이 훅을 호출할 때의 문법은 다음과 같다.
 
useEffect 문법과 비슷하게 두 가지 인수가 들어가는데,
첫 번째 인수로는 특정 조건에만 실행될 동작이 들어갈 콜백 함수,
두 번째 인수로는 조건이 될 의존성 배열이다.
 
우리는 이 의존성 배열을 deps라 부르는데,
deps안의 값이 변경 될 때에만 콜백 함수가 실행되며
콜백 함수가 반환하는 값을 useMemo가 그대로 반환하기 때문에
변수에 담아 저장할 수 있다.
 
useMemo(() => {}, []);
 
작성해둔 getAnalyzedData 함수 내부에 있던 코드를
콜백 함수 안으로 옮겨주고 기존에 있던 getAnalyzedData는 삭제하자.
 
useMemo(() => { console.log("getAnalyzedData 함수 호출!"); const totalCount = todos.length; const completeCount = todos.filter((todo) => todo.isComplete).length; const notCompleteCount = totalCount - completeCount; return { totalCount, completeCount, notCompleteCount, }; }, []);
 
구조 분해 할당으로 받던 값도 삭제하고 useMemo에서 반환하는 값을
구조 분해 할당으로 다시 받아주자.
 
const { totalCount, completeCount, notCompleteCount } = useMemo(() => { console.log("getAnalyzedData 함수 호출!"); const totalCount = todos.length; const completeCount = todos.filter((todo) => todo.isComplete).length; const notCompleteCount = totalCount - completeCount; return { totalCount, completeCount, notCompleteCount, }; }, []);
 
마지막으로 depstodos를 넣어주면 todos의 값이 변경될 때만
로직이 실행되어 불필요한 연산이 발생하는 것을 방지할 수 있다.
 
const { totalCount, completeCount, notCompleteCount } = useMemo(() => { console.log("getAnalyzedData 함수 호출!"); const totalCount = todos.length; const completeCount = todos.filter((todo) => todo.isComplete).length; const notCompleteCount = totalCount - completeCount; return { totalCount, completeCount, notCompleteCount, }; }, [todos]);
 
검색어를 입력해도 더 이상 해당 함수가 호출되지 않는다.
 
notion image
 
Share article

clubnerdy