리액트에서는 동일한 계산결과(자주필요한 값)를 최초 연산시 메모리에 캐싱하고 이후에 이 값을 필요할때 마다 다시 연산하지 않고 memoization 된 값을 재사용할 수 있도록 useMemo()
훅을 제공한다
const value = useMemo(() => {
return calculate();
},[item]);
value
Memoization 기법은 애플리케이션에 따라 실행 속도를 향상시키는데 도움을 줄 수 있지만, 무분별하게 사용되어서는 안된다. 그 이유는 memoization 기법 자체가 메모리라는 시스템 자원을 소비해서 저장하는 것이기 때문에 지나치게 사용되면 성능에 악영향을 줄 수 있다.
hardCalculator()
함수는 연산량이 많도록 임의로 루프를 돌도록 설정easyCalculator()
함수는 단순하게 값을 더해서 리턴하도록 설정hardSum
변수를 memoization하지 않을 경우 해당 컴포넌트 함수에서 easyNumber
(state)가 업데이트 되는 경우에도 렌더링이 발생하게 되어 hardCalculator
연산이 일어나게 될 것이다.
→ useMemo()를 사용해서 hardSum
변수를 hardNumber
값이 변경되었을 경우에 memoization 하도록 설정
import './App.css';
import {useEffect, useMemo, useState} from "react";
function hardCalculator(number) {
console.log("어려운 계산")
for (let i = 0; i < 999999999; i++) {}
return number + 10000;
}
function easyCalculator(number) {
console.log("쉬운 계산")
return number + 100;
}
function App() {
const [hardNumber, setHardNumber] = useState(1);
const [easyNumber, setEasyNumber] = useState(1);
// const hardSum = hardCalculator(hardNumber);
const hardSum = useMemo(() => {
return hardCalculator(hardNumber);
},[hardNumber])
const easySum = easyCalculator(easyNumber);
return (
<div className="App">
<h3>어려운 계산기</h3>
<input type="number" value={hardNumber} onChange={(e) => {
setHardNumber(parseInt(e.target.value))
}}/>
<span> + 10000 = {hardSum}</span>
<h3>쉬운 계산기</h3>
<input type="number" value={easyNumber} onChange={(e) => {
setEasyNumber(parseInt(e.target.value))
}}/>
<span> + 100 = {easySum}</span>
</div>
);
}
export default App;
→ 최초 렌더링 후 easyCalculator를 실행하여 전체 다시 렌더링이 되더라도 hardCalculator가 실행되지 않는다
memoization의 리턴값이 객체인 경우
import './App.css';
import {useEffect, useMemo, useState} from "react";
function App() {
const [number, setNumber] = useState(0);
const [isKorea, setIsKorea] = useState(true);
// const location = isKorea ? '한국' : '외국';
const location = useMemo(() => {
return {country: isKorea ? '한국' : '외국'}
},[isKorea]);
useEffect(() => {
console.log('useEffect 호출됨')
}, [location])
return (
<div className="App">
<h2>하루에 몇끼 드세요?</h2>
<input type="number" value={number} onChange={(e) => {
setNumber(e.target.value)
}}/>
<hr/>
<h2>지금 어디에 계세요?</h2>
<p>나라 : {location.country}</p>
<button onClick={() => {
setIsKorea(!isKorea)
}}>비행기 타자
</button>
</div>
);
}
export default App;
최초 useEffect 실행 이후 number를 올려 렌더링이 다시 일어나더라도 객체타입의 변수를 memoization 해두었기 때문에 useEffect가 호출되지 않음
useCallback()은 useMemo()와 동일하게 memoization기법을 사용하여 메모리에 캐싱하고 렌더링시 다시 캐싱된 값을 사용하는것에 공통점이 있다.
단, useMemo()와의 명백한 차이는 다음과 같다.
하는 것이다
import './App.css';
import {useCallback, useEffect, useState} from "react";
import Box from "./Box";
function App() {
const [number, setNumber] = useState(0);
const [toggle, setToggle] = useState(true);
const someFunc = useCallback(() => {
console.log('someFunc : number', number)
return;
}, [number]);
// const someFunc = () => {
// console.log('someFunc : number', number)
// return;
// }
useEffect(() => {
console.log("someFunc가 변경되었습니다. ")
}, [someFunc])
return (
<div className="App" style={{
backgroundColor: isDark ? 'black' : 'white'
}}>
<input type="number" value={number} onChange={(e) => {
setNumber(e.target.value)
}}/>
<button onClick={() => {
setToggle(!toggle)
}}>{toggle.toString()}</button>
<br/>
<button onClick={someFunc}>call SomeFunc</button>
</div>
);
}
export default App;
useEffect()의 의존성 배열에 someFunc라는 함수를 추가하여 변경이 일어날 경우 콘솔에 로그를 출력하도록 한다.
최초 호출시 someFunc가 memoization된다 → number를 변경한다(state) → 렌더링이 일어나고 컴포넌트 함수App이 재호출된다 → someFunc가 memoization된 함수의 주소를 가리키기 때문에 변화가 일어나지 않음 → useEffect가 실행되지 않는다
그러나 최초 렌더링시 memoization되었을 때의 number를 메모리에 그대로 캐싱하고 있기 때문에 버튼을 눌러 함수 호출 시 변화한 number를 표현하지 못한다.
의존성 배열에 number 추가
최초 호출시 someFunc가 memoization된다 → number를 변경한다(state) → 렌더링이 일어나고 컴포넌트 함수App이 재호출된다 → 의존성으로 가지고있는 number가 변했기 때문에 memoization이 일어난다. → useEffect()가 실행된다
number 가 변하는 상황을 제외한 다른 렌더링 상황에서는 memoization된 함수가 변하지 않으므로 useEffect()가 실행되지 않는다