useMemo는 컴포넌트 성능을 최적화하는 데 사용되는 리액트 훅 중 하나이다.
useMemo에서 Memo는 memoization을 뜻하는데,
이는 동일한 값을 리턴하는 함수를 반복적으로 호출해야 될 때
맨 처음 값을 메모리에 저장해서 필요할 때마다 재사용하는 기법이다.
useMemo는 처음 계산된 결괏값을 메모리에 저장해서
컴포넌트가 반복적으로 렌더링되어도 해당 함수를 계속 호출하지 않고,
메모리에 저장해둔 값을 재사용할 수 있게 해 준다.
useMemo는 두 개의 인자를 받는다.
첫 번째는 콜백함수, 두 번째는 배열이다.
// useMemo(콜백함수, 배열)
const value = useMemo(() => {
return calculate();
}, [item]);
콜백함수가 리턴하는 값이 useMemo가 리턴하는 값이 된다.
위 코드 상에서 calculate 함수의 결괏값이 value가 되는 것이다.
두 번째 인자인 배열은 의존성 배열로, 해당 배열 요소 값이 업데이트될 때만
콜백함수를 다시 호출하여 memoization 된 값을 업데이트해서 다시 memoization을 한다.
만약 두 번째 인자로 빈 배열을 넣어주었다면
해당 컴포넌트가 처음 마운트됐을 때만 값을 계산하고, 이후에는 저장된 값을 재사용한다.
useMemo를 사용한다는 것은 재사용되는 값을 따로 저장하기 위해
메모리를 소비하는 것이므로 불필요한 값을 memoization 하면 성능이 악화될 수 있다.
import React, { useState } from 'react';
const hardCalculate = (num) => {
console.log("어려운 계산");
for (let i=0; i<999999; i++) {}
return num + 10000;
}
const esayCalculate = (num) => {
console.log("쉬운 계산");
return num + 1;
}
function App() {
const [hardNum, setHardNum] = useState(1);
const [easyNum, setEasyNum] = useState(1);
const hardSum = hardCalculate(hardNum);
const easySum = esayCalculate(easyNum);
return (
<div>
<h3>어려운 계산기</h3>
<input
type="number"
value={hardNum}
onChange={(e) => setHardNum(parseInt(e.target.value))}
/>
<span> + 10000 = {hardSum}</span>
<h3>쉬운 계산기</h3>
<input
type="number"
value={easyNum}
onChange={(e) => setEasyNum(parseInt(e.target.value))}
/>
<span> + 1 = {easySum}</span>
</div>
);
}
export default App;
위 코드를 실행해보면 쉬운 계산기의 숫자만 바꿨는데
hardCalculate 함수도 실행되어 콘솔에 찍히는 것을 볼 수 있다.
이것은 위 컴포넌트가 함수형 컴포넌트이기 때문이다.
state의 값이 바뀌면 해당 컴포넌트가 다시 렌더링 되는데,
이때 변수 hardSum과 easySum이 초기화되므로 hardCalculate 함수가 실행되는 것이다.
여기서 easyNum state를 변경할 때 hardCalculate 함수가 실행되지 않게 하려면 useMemo를 사용하면 된다.
useMemo는 어떠한 조건이 만족됐을 때만 특정 변수가 초기화되게 할 수 있다.
특정 조건에 만족하지 않으면 해당 컴포넌트가 렌더링 되어 내부 변수를 초기화하더라도
조건에 만족하지 않는 변수는 이전에 가지고 있던 값을 사용하는 것이다.
이것을 memoization이라고 한다.
const hardSum = useMemo(() => {
return hardCalculate(hardNum)
}, [hardNum]);
기존 const hardSum = hardCalculate(hardNum)을 useMemo을 사용하여 바꿔주었다.
의존성 배열 부분에는 hardNum을 넣어 hardNum state가 바뀔 때만 hardCalculate 함수를 실행하게 했다.
그 결과 이전과는 다르게 쉬운 계산기의 숫자가 바뀌면
easyCalculate 함수만 실행되는 것을 확인할 수 있다.
반면 어려운 계산기의 숫자가 바뀌면 여전히 두 개의 함수가 실행되는 것을 볼 수 있다.
(easySum은 memoization을 하지 않았기 때문이다.)
또한 useMemo는 객체타입을 memoization 할 때 자주 사용된다.
import React, { useEffect, useState } from 'react';
function App() {
const [num, setNum] = useState(0);
const [isKorea, setIsKorea] = useState(true);
const location = {
country: isKorea ? '한국' : '외국'
};
useEffect(() => {
console.log('useEffect 호출');
}, [location]);
return (
<div>
<h2>하루에 몇 끼 먹어요?</h2>
<input
type="number"
value={num}
onChange={(e) => setNum(e.target.value)}
/>
<hr/>
<h2>어느 나라에 있어요?</h2>
<p>나라: {location.country}</p>
<button onClick={() => setIsKorea(!isKorea)}>비행기 타자</button>
</div>
);
}
export default App;
위 코드를 실행하면 location을 바꾸지 않아도 num state가 바뀔 때마다 useEffect가 실행된다.
그 이유는 location이 객체타입이기 때문이다.
객체타입은 변수에 메모리 상의 주소가 들어가 있어서
아무리 값이 같아도 주소가 달라 다른 변수이다.
따라서 num state를 변화시켰을 때 App 컴포넌트가 렌더링 되면서
location 변수가 재할당받게 된다.
그러면 다른 주소를 가진 location이 되므로 location이 바뀌어 useEffect가 실행되는 것이다.
이럴 때 useMemo를 사용하여 해당 컴포넌트가 렌더링 됐을 때
location 변수가 초기화되는 것을 막아줄 수 있다.
const location = useMemo(() => {
return {
country: isKorea ? '한국' : '외국'
}
}, [isKorea]);
useMemo를 사용하여 location을 memoization 해주어
isKorea state가 바뀌었을 때만 초기화되게 해 주었다.
그러면 num state를 바꾸었을 때 useEffect가 실행되지 않는 것을 볼 수 있다.
따라서 불필요한 함수 실행을 줄이기 위해 useMemo를 사용하여
컴포넌트의 성능을 최적화할 수 있다.
참고자료
'Web > React' 카테고리의 다른 글
[React] 이미지 에러 시 대체 이미지 설정 (0) | 2023.01.02 |
---|---|
[React] styled-components props (0) | 2023.01.02 |
[React] useRef (실제 DOM 접근) (0) | 2022.12.23 |
[React] useRef (useState와 비교) (0) | 2022.12.21 |
[React] JSX 안에서 중첩 삼 항 연산자 사용하기 (0) | 2022.11.17 |