원하는 화면과 기능은 만들수 있는데 왜 렌더 횟수가 더 많은지 모를경우가 많았다. 이제 렌더링을 최적화에 대해서 공부한다 먼저 useMemo!
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
// 출처 공식문서
useMemo는 첫번째 인자로 callback 함수를 두번째인자로 의존성 배열을 받는다. 그리고 첫번째 인자인 callback함수의 return 값을 메모이제이션한다. 두번째인자인 의존선배열에 있는 값이 봐끼는 경우만 useMemo가 제 실해되어 다시 메모이제이션된다.
메모이제이션은 의존성배열이 봐끼지않으면 해당 값을 저장하고 있는것이다. 이저장된 값은 리렌더가 되더라도 새로 생성성되지않고 저장된 값을 다시 사용하게된다.
공식문서 예시에도 나와있듯이 computeExpensiveValue
, 즉 복잡하고 무거운 계산의 결과값같은 경우 렌더시마다 동일한 값이지만 새로 계산을 하는것은 비효율 적이기때문에 메모이제이션을 통해 효율적으로 사용할 수 있도록 하는것이다.
복잡한 계산
이 필요한 값을 이용할때, 이 값은 매번 렌더가 될 때마다복잡한 계산
이 실행된 결과 값인경우!
결과값은 그대론데 매번 새로 계산해준다면 너무나 비효율적일 것!!
import React, { useState } from 'react';
const computeExpensiveValue = (count) => {
for (let i = 0; i < 999999999; i++) {}
console.log('복잡한계산');
return count;
};
const Test = () => {
const [count, setCount] = useState(0);
const [capture, setCapture] = useState(0);
const currentCount = computeExpensiveValue(count);
const countUp = () => {
setCount(count + 1);
};
const captureCount = () => {
setCapture(count);
};
return (
<div>
<p>Current Count : {currentCount}</p>
<p>Capture Count : {capture}</p>
<button onClick={countUp}>count Up</button>
<button onClick={captureCount}>capture count</button>
</div>
);
};
export default Test;
capture count
버튼을 누르면 Current Count를 Capture로 지정해주면 되는 간단한 동작이다.
하지만! 함수 컴포넌트가 리렌더링이된다는건 함수가 다시 실행 된다는 것! ➪ 함수내의 변수는 모두 초기화된다.
➪currentCount
도 값이 봐끼지 않았지만 초기화된후 재할 당이 되면서복잡한계산
이 재실행 된다.😱
결과적으로 변하지 않은 값이지만 계산을 다시하는 아주 비효율적인 동작을 하게 되는 것이다.
import React, { useMemo, useState } from 'react'; // useMemo import
const computeExpensiveValue = (count) => {
for (let i = 0; i < 999999999; i++) {}
console.log('복잡한계산');
return count;
};
const Test = () => {
const [count, setCount] = useState(0);
const [capture, setCapture] = useState(0);
// useMemo를 사용하여 변수지정, 의존성으로 count 지정.
const currentCount = useMemo(() => computeExpensiveValue(count), [count]);
const countUp = () => {
setCount(count + 1);
};
const captureCount = () => {
setCapture(count);
};
return (
<div>
<p>Current Count : {currentCount}</p>
<p>Capture Count : {capture}</p>
<button onClick={countUp}>count Up</button>
<button onClick={captureCount}>capture count</button>
</div>
);
};
export default Test;
currentCount
값이 useMemo의 반환 값으로 지정을 해두었다. 의존성으로count
를 지정해두어count
가 변화하면 useMemo가 재실행 된다. gif를 보면count Up
버튼을 눌럿을때 복잡한계산이 실행되고,capture count
를 누르면 복잡한 계산이 재실행 되지 않는걸 확인 할 수있다. useMemo를 사용하면 리렌더가 되더라도 해당 변수가 초기화 되지않고 메모리에 저장되어있는 값을 그대로 사용하게된다.
➪ 불필요한 계산이 줄었다!!
import React, { useEffect, useMemo, useState } from 'react';
const UseMemo2 = () => {
const [random, setRandom] = useState(0);
const [count, setCount] = useState(0);
const fruits = ['apple', 'banana', 'melon', 'peach', 'mango'];
const fruit = { fruit: fruits[random] };
const shuffleHandler = () => {
setRandom(Math.floor(Math.random() * 5));
};
const countUp = () => {
setCount(count + 1);
};
useEffect(() => {
console.log(`useEffect 실행`);
}, [fruit]);
return (
<div>
<p>Current Fruit : {fruit.fruit}</p>
<p>Count : {count}</p>
<button onClick={shuffleHandler}>shuffle!</button>
<button onClick={countUp}>countUp!</button>
</div>
);
};
export default UseMemo2;
useEffect
는fruit
를 의존성 배열로 갖고 있다.shuffle
버튼을 통해 fruit 값을 변경하면useEffect
가 실행되는 것 당연하다. 하지만!countUp
을 클릭했을때도useEffect
가 실행되는 것을 확인 할 수 있다.
➪fruit
는 객체다. 컴포넌트가 리렌더 될때마다 새로 참조값이 생성되게 된다. 객체가 가지고 있는 키-값 은 동일하지만 참조주소가 달라지는 것이다. 이렇기 때문에countUp
버튼을 눌렀을때 리렌더가되고,fruit
는 새로운 참조 주소값을 갖는 새로운 객체가 되기 때문에useEffect
가 의도치 않게 실행 되는 것이다.
import React, { useEffect, useMemo, useState } from 'react';
const UseMemo2 = () => {
const [random, setRandom] = useState(0);
const [count, setCount] = useState(0);
const fruits = ['apple', 'banana', 'melon', 'peach', 'mango'];
//useMemo 사용
const fruit = useMemo(
() => ({
fruit: fruits[random],
}),
[random]
);
const shuffleHandler = () => {
setRandom(Math.floor(Math.random() * 5));
};
const countUp = () => {
setCount(count + 1);
};
useEffect(() => {
console.log(`useEffect 실행`);
}, [fruit]);
return (
<div>
<p>Current Fruit : {fruit.fruit}</p>
<p>Count : {count}</p>
<button onClick={shuffleHandler}>shuffle!</button>
<button onClick={countUp}>countUp!</button>
</div>
);
};
export default UseMemo2;
useMemo
를 사용해서 fruit를 할당하고, 의존성 배열에random
을 추가했다. 이렇게 되면countUp
버튼 클릭에 따른 리렌더시furit
객체는 memoization 되어 새로운 객체 참조 주소값이 생성되지않아서useEffect
실행되지 않아 렌더 최적화가 가능해진다!