2월 12일 여정 35일차이다.
이전에 useCallback, React.memo를 보았다. 하나는 함수를 캐싱하는 것이고, 하나는 컴포넌트를 캐싱하였다. 이번에 볼 Hook은 useMemo이다.
useMemo는 무엇을 캐싱할까?
결과적으로 useMemo는 값을 캐싱한다. 가령 예를 들어 동일한 값을 반환하는 함수가 있다고 하자. 만약 그 함수를 리랜더링이 될때마다 계속 호출해야 된다면 필요없는 랜더링이 계속 일어날 것이다.
그때 useMemo를 사용하면 된다. 밑에 코드를 한번 보자.
function HeavyButton() {
const [count, setCount] = useState(0);
const heavyWork = () => {
for (let i = 0; i < 1000000000; i++) {}
return 100;
};
// CASE 1 : useMemo를 사용하지 않았을 때
const value = heavyWork();
// CASE 2 : useMemo를 사용했을 때
// const value = useMemo(() => heavyWork(), []);
return (
<>
<p>나는 {value}을 가져오는 엄청 무거운 작업을 하는 컴포넌트야!</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
누르면 아래 count가 올라가요!
</button>
<br />
{count}
</>
);
}
export default HeavyButton;
엄청 무거운 작업을 수행하는 함수가 있다. heavyWork()라는 함수는 버튼을 클릭하면 1000000000만큼 for문을 돌고 100을 반환한다.
const value = heavyWork();
이렇게 useMemo를 사용하지 않고 이 코드를 실행시킨다면 한번 클릭할 때마다 리렌더링 시간이 오래 걸릴 것이다. 그러나 밑에와 같이 useMemo를 사용하여 이전에 있는 값을 메모리에 저장하고 그 값만 불러와서 사용하게 된다면 렌더링 속도는 빨라질 것이다.
const value = useMemo(() => heavyWork(), []);
다른 예제를 한번 더 보자.
function ObjectComponent() {
const [isAlive, setIsAlive] = useState(true);
const [uselessCount, setUselessCount] = useState(0);
const me = {
name: "Ted Chang",
age: 21,
isAlive: isAlive ? "생존" : "사망",
};
useEffect(() => {
console.log("생존여부가 바뀔 때만 호출해주세요!");
}, [me]);
return (
<>
<div>
내 이름은 {me.name}이구, 나이는 {me.age}야!
</div>
<br />
<div>
<button
onClick={() => {
setIsAlive(!isAlive);
}}
>
누르면 살았다가 죽었다가 해요
</button>
<br />
생존여부 : {me.isAlive}
</div>
<hr />
필요없는 숫자 영역이에요!
<br />
{uselessCount}
<br />
<button
onClick={() => {
setUselessCount(uselessCount + 1);
}}
>
누르면 숫자가 올라가요
</button>
</>
);
}
export default ObjectComponent;
하나는 버튼을 누르면 카운트가 증가하고 나머지 버튼은 생존과 죽음으로 바꾸어주는 버튼이다.
useEffect로 me라는 객체 안에 '생존여부가 바뀔 때만 호출해주세요'라고 작성해놨다. 그러나 의존성 배열에 me를 넣었음에도 불구하고 카운트가 증가하는 버튼을 누르면 '생존여부가 바뀔 때만 호출해주세요'가 호출되는 것을 볼 수 있다. 왜 그럴까?
Count 버튼을 누르게 되면 uselessCount state가 바뀐다. state가 바뀌게 되면 결국
리렌더링이 일어난다. 그럼 me라는 객체도 새롭게 다른 메모리 주소에 할당을 받게 된다. 결과적으로 useEffect에서는 메모리가 새롭게 바뀌었기 때문에 자신의 역할을 하지못하고 '생존여부가 바뀔 때만 호출해주세요'가 호출되는 것이다.
그럼 어떻게 useMemo를 사용하여 해결할까?
const me = useMemo(() => {
return {
name: "Ted Chang",
age: 21,
isAlive: isAlive ? "생존" : "사망",
};
}, [isAlive]);
위와 같이 써주면 me라는 객체가 메모리에 저장되고 아무리 리렌더링이 일어나더라도 메모리 주소가 변하지 않기 때문에 '생존여부가 바뀔 때만 호출해주세요'가 출력되지 않게된다.
그러나 useMemo도 너무 많이 사용하게 되면 오히려 성능이 악화 될수 있다고 한다.
useCallback, useMemo, React.Memo 3개는 결과적으로 최적화(Optimization)를 위해서 존재한다. 즉 비용 발생을 최대한으로 줄이기 위한 것이다.