useMemo는 컴포넌트의 성능을 최적화시킬 수 있는 대표적인 react hooks중 하나다.
useMemo에서 Memo는 Memoization을 뜻함.
Memoization은
동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다.
시작하기 앞서 함수형 컴포넌트에 대해 꼭 알아야한다.
함수형 컴포넌트는 '렌더링 → Component 함수 호출 → 모든 내부 변수 초기화' 의 순서를 거친다.
Component가 렌더링이 될 때마다 value라는 변수가 초기화되는데, 따라서 calculate 함수는 반복적으로 호출된다. calculate 함수가 무거운 일을 하는 함수라면 매우 비효율 적일것이다.
function Component() {
const value = calculate();
return <div>{value}</div>
}
function calculate() {
return 10;
}
useMemo를 사용하면 '렌더링 → Component 함수 호출 → Memoize된 함수를 재사용'의 동작 순서를 거친다.
useMemo를 사용해서 memoization을 해주면 calculate 함수를 반복적으로 실행 할 필요가 없다.
useMemo는 처음에 계산된 결괏값을 메모리에 저장해서 컴포넌트가 반복적으로 렌더링이 되어도 계속 calculate를 다시 호출하지 않고, 이전에 이미 계산된 결과값을 메모리에서 꺼내와서 재사용 할 수 있게 한다.
useMemo의 첫 번째 인자 콜백함수를, 두 번쨰 인자로 의존성 배열을 받는다.
두 번째 인자인 배열의 요소 값이 업데이트 될 때만 콜백 함수를 다시 호출해서 memoization된 값을 업데이트 해줘서 다시 memoization을 해준다.
만약 빈 배열([])을 넘겨주면 맨 처음 컴포넌트가 마운트 되었을 때만 값을 계산하고 이후에는 항상 memoization된 값을 꺼내와서 사용한다.
// 첫 번째 인자 콜백함수
// 두 번째 인자 의존성배열
const value = useMemo(() => calculate(),[item])
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
값을 재활용하기 위해 따로 메모리를 소비해서 저장을 해놓는 것이다. 그렇기 때문에 불필요한 값을 모두 Memoization 해버리면 성능이 안좋아질 수 있기 때문에, 필요할 때만 사용한다.
import { useMemo } from 'react';
const location = { country: isKorea ? '한국' : '일본' };
useEffect(() => {
console.log('useEffect... 호출');
}, [location])
object는 객체 타입이어서 일반 원시 타입과는 다르게, 값이 저장될 떄 주소 값으로 저장이 된다.
즉 메모리 상의 주소가 다르게 저장되었다는 말이다.
const location = { country: isKorea ? '한국' : '일본' }; 눈으로 보이기에는 똑같지만
저장된 메모리 상의 주소가 완전히 다르기 때문에 useEffect의 location은 변경되었다고 생각할 수 있다.
따라서 이것을 해결해주려면 useMemo를 아래와 같이 사용해서 memoization 해주면 된다.
import { useMemo, useEffect, useState } from 'react';
function App() {
const [number, setNumber] = useState(0);
const [isKorea, setIsKorea] = useState(true);
// const location = { country: isKorea ? '한국' : '일본' };
const location = useMemo(() => {
return {
country: isKorea ? '한국' : '일본'
}
}, [isKorea])
useEffect(() => {
console.log('useEffect... 호출');
// 뭔가 오래 걸리는 작업
}, [location])
return (
<header className="App-header">
<h2>하루에 몇 끼 먹어요?</h2>
<input type="number" value={number} onChange={(e) => setNumber(e.target.value)}/>
<hr/>
<h2>어느 나라에 있어요?</h2>
<p>나라: {location.country}</p>
<button onClick={() => setIsKorea(!isKorea)}>Update</button>
</header>
);
}
export default App;