useMemo
는 리액트에서 값
을 memoization 할 수 있도록 해주는 함수다. useMemo는 두 가지 인자를 받는데, 첫 번째 인자는 콜백 함수이며 이 함수에서 리턴하는 값이 memoization 된다. 그리고 의존성 배열을 두 번째 인자로 받는다. 두 번째 인자에 빈 배열([]
)을 전달하면 첫 렌더링시에만 메모이제이션을 수행하고 이후에는 항상 메모이제이션 된 값을 꺼내와서 사용한다.// useMemo(callbackFunction, deps]
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
memoization을 할 때 주의해야 할 점은 만약 새로운 값을 만들어서 사용해야 하는 상황임에도 불구하고 이전의 결과를 그대로 활용해버리면 버그가 발생할 수 있다.
위의 예시에서 a, b 라는 두 가지 변수를 이용해서 메모이제이션 하기 위한 값을 계산하고 있다. 그런데 만약 a, b 라는 값이 변경되었는데 이전의 값을 그대로 활용해버리면 의도한 결과와 다른 결과가 나오게 될 것이다.
이런 상황을 방지하기 위해 useMemo에서는 의존성 배열을 인자로 받아, 의존성 배열에 있는 값 중 하나라도 이전 렌더링과 비교했을 때 변경되었다면 메모된 값을 활용하는 것이 아니라 새로운 값을 다시 계산한다.
아래의 예시에서 delayCalculate
함수는 계산이 오래걸리는 함수이고, quickCalculate
함수는 계산이 빨리 끝나는 함수이다. "delayCalculate"의 숫자를 증가시켜보면 콘솔창에 찍히는데 딜레이가 걸리는 걸 볼 수 있다.
그러나 계산이 빨리 끝나는 "quickCalculate"의 숫자를 증가시켜보아도 똑같이 딜레이가 걸리는 현상을 볼 수 있다. 이는 "quickCalculate"의 숫자 증가 시 quickNumber
의 state가 변경되면서 컴포넌트가 리렌더링 되기 때문에 quickCalculate
의 함수와 함께 delayCalculate
함수도 초기화 되면서 다시 호출되기 때문이다.
import React, { useState, useMemo } from 'react';
const delayCalculate = (num) => {
console.log('delay..');
// 복잡한 계산으로 가정하기 위해 일부터 딜레이를 주기 위한 for문
for (let i = 0; i < 999999999; i++) { }
return num + 10;
}
const quickCalculate = (num) => {
console.log('quick!');
return num + 1;
}
function App() {
const [delayNumber, setDelayNumber] = useState(0);
const [quickNumber, setQuickNumber] = useState(0);
const delaySum = delayCalculate(delayNumber);
const quickSum = quickCalculate(quickNumber);
return (
<>
<div>delayCalculate</div>
<input type='number'
value={delayNumber}
onChange={(e) => setDelayNumber(parseInt(e.target.value))} />
<span> + 10 = {delaySum}</span>
<div>quickCalculate</div>
<input type='number'
value={quickNumber}
onChange={(e) => setQuickNumber(parseInt(e.target.value))} />
<span> + 1 = {quickSum}</span>
</>
);
}
export default App;
그러나 quickCalculate
의 값이 변할 때 마다 delayCalculate
의 값도 변할 필요가 없으므로, 이 값을 저장해두었다가 다시 꺼내 쓸 수 있다면 굳이 delayCalculate
함수를 호출할 필요도 없을 것이다.
아래와 같이 useMemo
로 호출하여 delayCalculate
를 감싸주면, 이전에 구축된 렌더링과 새로이 구축되는 렌더링을 비교해 num
값이 동일한 경우에는 이전 렌더링의 num
값을 그대로 재활용할 수 있게 된다. 그래서 "delayCalculate" 값 변경 시 여전히 딜레이가 있지만 "quickCalculate"의 값이 변할 때는 "delayCalculate"는 이전에 저장해둔 값을 꺼내오기 때문에 "delayCalculate"가 다시 호출되지 않고 딜레이도 안 걸리는 모습을 볼 수 있다.
이는 메모이제이션(Memoization) 개념과 관계가 있다.
import React, { useState, useMemo } from 'react';
const delayCalculate = (num) => {
console.log('delay..');
for (let i = 0; i < 999999999; i++) { }
return num + 10;
}
const quickCalculate = (num) => {
console.log('quick!');
return num + 1;
}
function App() {
const [delayNumber, setDelayNumber] = useState(0);
const [quickNumber, setQuickNumber] = useState(0);
// useMemo로 감싸준 계산이 오래 걸리는 delayCalculate 함수
const delaySum = useMemo(() => {
return delayCalculate(delayNumber)
}, [delayNumber]);
const quickSum = quickCalculate(quickNumber);
return (
<>
<div>delayCalculate</div>
<input type='number'
value={delayNumber}
onChange={(e) => setDelayNumber(parseInt(e.target.value))} />
<span> + 10 = {delaySum}</span>
<div>quickCalculate</div>
<input type='number'
value={quickNumber}
onChange={(e) => setQuickNumber(parseInt(e.target.value))} />
<span> + 1 = {quickSum}</span>
</>
);
}
export default App;
Memoization
- 메모이제이션(Memoization)은 알고리즘에서 자주 나오는 개념으로, 기존에 수행한 연산의 결과값을 메모리에 저장을 해두고, 동일한 입력이 들어오면 재활용하는 프로그래밍 기법을 말한다. 이 메모이제이션을 적절히 사용한다면 굳이 중복 연산을 할 필요가 없기 때문에 앱의 성능을 최적화할 수 있다.
useMemo
는 이 개념을 이용하여 복잡한 연산의 중복을 피하고 React 앱의 성능을 최적화시킨다. 직접 메모이제이션 개념을 이용하여 로직을 구현할 수도 있으나,useMemo
Hook을 호출하면 이런 로직을 직접 구현하는 것을 대신해주기 때문에 훨씬 간편하다.