useMemo와 useCallback에 대해 알아보기 전 메모이제이션 개념에 대해 간단히 알아보자.
컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다.
memoization을 사용하는 useMemo 함수를 알아보자.
useMemo(()=>fn,deps);
예제를 통해 이해해보자.
(예제 내용은 '리액트를 다루는 기술' 책을 참고했다.)
평균 값을 계산하는 컴포넌트를 만들어보자.
import React, { useState } from "react";
const getAverage = (numbers) => {
console.log("calculate average...");
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState("");
const onChange = (e) => {
setNumber(e.target.value);
};
const onInsert = () => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber("");
};
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<ul>
{list.map((value, idx) => (
<li key={idx}>{value}</li>
))}
</ul>
<div>
<b>평균값 : </b>
{getAverage(list)}
</div>
</div>
);
};
export default Average;
그 런 데
약간 거슬리는 부분이 있다.
input 에 값을 입력할때마다 getAverage 함수가 계속 호출돼서 콘솔에 찍히는 것이 보인다.
'등록' 버튼을 누르기 전에는 굳이 평균계산을 해줄 필요가 없다. 그렇기때문에 렌더링될 때마다 평균 값이 계산되는 것은 낭비이다.
이러한 문제를 useMemo를 사용해서 최적화할 수 있다.
useMemo를 사용해서 렌더링 과정에서 특정 값이 바뀔때만 연산을 실행하도록 하고, 값에 변화가 없으면 이전에 연산했던 결과를 재사용할 수 있다.
useMemo를 사용해서 위 예제를 수정해보자.
const avg = useMemo(() => getAverage(list), [list]);
우선 getAverage 함수를 실행하는 함수를 따로 만들어주었다.
그리고 이 함수를 useMemo를 사용해 감싸주었다.
useMemo의 두 번째 인자의 배열에 list를 넣어줘서 list에 변화가 있을때 함수가 실행될 수 있도록 했다.
실행해보면 이제 input에 값을 입력해도 getAverage 함수가 실행되지 않는 것을 볼 수 있다.
즉, useCallback은 메모이제이션된 함수를 반환하는 함수이다
위에서 작성했던 예제 중에
const onChange = (e) => {
setNumber(e.target.value);
};
const onInsert = () => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber("");
};
이 부분을 살펴보자.
input 값이 변경될 때 실행되는 onChange 함수와 '등록'버튼을 눌렀을 때 값을 리스트에 추가하는 함수이다.
이 두 함수는 컴포넌트가 리렌더링될 때마다 새로 만들어진 함수를 사용하게 된다.
인풋값이 바뀔 때마다 onChange 함수를 다시 선언해 줄 필요는 없다.
따라서 우리는 이 함수들을 다음과 같이 바꿔줄 수 있다.
const onChange = useCallback((e) => {
setNumber(e.target.value);
},[]);
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber("");
},[number,list]);
onChange는 조회하는 값 없이 number을 설정해주기만 한다. 의존할 값이 없기 때문에 빈 배열을 넘겨준다.
(첫 렌더링시 함수 선언)
onInsert는 number와 list의 값을 가져와서 새로운 list을 설정해준다. 따라서 변화하는 number와 list의 값이 함수에 반영되어져야 한다. 그래서 두 번째 인자에 [number,list]를 넘겨줘서 해당 값이 변화할 때 함수가 생성되도록 해주었다.
이번 포스팅에서는 렌더링 최적화 함수인 useMemo와 useCallback 함수를 알아보았다. 두 함수 모두 중복되는 선언과 실행을 줄여서 성능을 최적화 한다는 공통점이 있다.
차이점은 간단하게 설명하면
useMemo는 함수 실행 결과 값을 반환하고,
useCallback은 새로운 함수를 반환 한다는 것이다.
프로젝트를 하면서 useMemo와 useCallback은 사용해본 경험이 없는데 앞으로는 오늘 배운 내용을 바탕으로 컴포넌트 렌더링 최적화를 해봐야겠다.