useMemo를 사용하면 함수 컴포넌트 내부에서 발생하는 연산을 최적화 할 수 있다.
useMemo에서 memo는 memoization을 뜻하는데, 해석하면 '메모리에 넣는다'는 의미이다.컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행속도를 빠르게 하는 기술이다.
만약 동일한 값을 반환하는 함수를 반복적으로 호출해야 한다면, 처음값을 계산할 때, 해당 값을 메모리에 저장해 필요할 때마다 다시 계산하지 않고 메모리에서 꺼내서 재사용하는 것이다.
리액트에서 함수형 컴포넌트는
랜더링 -> 컴포넌트 함수 호출 -> 모든 내부 변수 초기화
의 순서를 거친다.
function Component() {
const value = calculate();
return <div>{value}</div>
}
function calculate() {
return 10;
}
컴포넌트가 렌더링 될 때마다 value 변수가초기화된다 그런데 만약 calculate함수가 무겁고 복잡한 작업을 요하는 함수라면 매우 비효율적일것이다.
useMemo를 사용하면
렌더링 -> 컴포넌트 함수 호출 -> memoize된 함수를 재사용
하는 순서를 거친다.
const value = useMemo(()=> {콜백함수}, [item]);
useMemo는 첫번째 인자로 콜백함수, 두번째 인자로 의존성배열을 받는다.
의존성 배열 안의 값이 업데이트될 때만 콜백함수를 다시 호출하여 메모리에 저장된 값을 업데이트 해준다.
(빈배열을 넣는다면 마운트 될 때만 값을 계산하고 그 이후로는 memoize된 값을 꺼내와 사용한다.)
const getAverage = (numbers : any) => {
console.log('평균값 계산중');
if(numbers.length === 0 ) return 0;
const sum = numbers.reduce((a:any,b:any) => a+b );
return sum/numbers.length;
}
const Average = () =>{
const [list, setList] = useState<number []>([]);
const [number, setNumber] = useState('');
const onChange = (e:any) => {
setNumber(e.target.value);
}
const onInsert = (e: any) => {
let paresed = parseInt(number);
const nextList = list.concat(paresed);
setList(nextList);
setNumber('');
}
console.log('average 컴포넌트 랜더링');
return(
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((v, i) => (
<li key={i}>{v}</li>
))}
</ul>
<div>
<b>평균값: </b> {getAverage(list)}
</div>
</div>
)
}
리스트에 숫자를 추가하면 추가된 숫자들의 평균을 보여주는 average함수컴포넌트이다. 브라우저에 숫자들을 등록하면 평균값이 잘 나타난다.
그러나 숫자를 등록할때 뿐만이 나리ㅏ input내용이 수정될 때도 getAverage함수가 호출된다. input내용이 수정될때는 평균값을 다시 계산할 필요가 없기 때문에 비효율적이다.
const avg = useMemo(() => getAverage(list), [list]);
useMemo의 첫번째 인자로 콜백함수를 넣고 의존성 배열안에 getAverage함수를 넣어 list값이 변경될 때만 콜백함수가 실행되도록 하면 된다.
-> 위 링크에 첨부된 좋은 예시를 가져옴
import { useMemo, useEffect, useState } from "react";
function App() {
const [number, setNumber] = useState(1);
const [isKorea, setIsKorea] = useState(true);
// 1번 location
// const location = {
// country: isKorea ? "한국" : "일본"
// };
// 2번 location
const location = useMemo(() => {
return {
country: isKorea ? '한국' : '일본'
}
}, [isKorea])
useEffect(() => {
console.log("useEffect 호출!");
}, [location]);
return (
<header className="App-header">
<h2>하루에 몇 끼 먹어요?</h2>
<input
type="number"
value={number}
onChange={(e) => setNumber(e.target.value)}
/>
<hr />
<h2>내가 있는 나라는?</h2>
<p>나라: {location.country}</p>
<button onClick={() => setIsKorea(!isKorea)}>Update</button>
</header>
);
}
""useEffect의 의존성 배열에 location을 넣었는데 number state를 변경해도 useEffect가 실행된다.
그 이유는 자바스크립트에서 객체는 원시 타입과는 다르게 값이 저장될 때 주소 값으로 저장되기 때문이다.
그렇기 때문에 리액트에선 number state가 바뀌면 App 컴포넌트가 재호출되면서 location의 주소값이 변경이 되었기 때문에 location이 변경이 되었다고 인식을 한다.
여기서도 useMemo 훅을 통해 이를 방지할 수 있다.
위 식에서 주석을 통해 1번 location과 2번 location을 구분해놨으니 두개를 번갈아가며 사용해보면 어떤 차이가 있는지 확인할 수 있다""
<참고한 블로그와 책>
<리액트를 다루는 기술>
[https://velog.io/@jinyoung985/React-useMemo%EB%9E%80]