useMemo와 useCallback에 대해서 알기위해서는 먼저 메모이제이션(momoization)에 대해서 알아야 하는데요
컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 똑같은 계산을 반복 수행하는 것이 아니라 이전에 계산한 값을 메모리에 저장해두고 사용함으로써 프로그램 실행 속도를 빠르게 하는 기술을 말합니다
다음 예제를 보면서 자세히 알아봅시다
여기 부모 컴포넌트인 App 컴포넌트와 자식 컴포넌트인 Info 컴포넌트가 있습니다
import React, { useState } from 'react';
import Info from './Info';
const App = () => {
const [color, setColor] = useState('');
const [movie, setMovie] = useState('');
const onChangeHandler = e => {
if (e.target.id === 'color') setColor(e.target.value);
else setMovie(e.target.value);
};
return (
<div className='App'>
<div>
<label>
What is your favorite color of rainbow ?
<input id='color' value={color} onChange={onChangeHandler} />
</label>
</div>
<div>
What is your favorite movie among these?
<label>
<input
type='radio'
name='movie'
value='Marriage Story'
onChange={onChangeHandler}
/>
Marriage Story
</label>
<label>
<input
type='radio'
name='movie'
value='The Fast And The Furious'
onChange={onChangeHandler}
/>
The Fast And The Furious
</label>
<label>
<input
type='radio'
name='movie'
value='Avengers'
onChange={onChangeHandler}
/>
Avengers
</label>
</div>
<Info color={color} movie={movie} />
</div>
);
};
export default App;
import React, { useMemo } from 'react';
const getColorKor = color => {
console.log('getColorKor');
switch (color) {
case 'red':
return '빨강';
case 'orange':
return '주황';
case 'yellow':
return '노랑';
case 'green':
return '초록';
case 'blue':
return '파랑';
case 'navy':
return '남';
case 'purple':
return '보라';
default:
return '레인보우';
}
};
const getMovieGenreKor = movie => {
console.log('getMovieGenreKor');
switch (movie) {
case 'Marriage Story':
return '드라마';
case 'The Fast And The Furious':
return '액션';
case 'Avengers':
return '슈퍼히어로';
default:
return '아직 잘 모름';
}
};
const Info = ({ color, movie }) => {
const colorKor = getColorKor(color);
const movieGenreKor = getMovieGenreKor(movie);
return (
<div className='info-wrapper'>
제가 가장 좋아하는 색은 {colorKor} 이고, <br />
즐겨보는 영화 장르는 {movieGenreKor} 입니다.
</div>
);
};
export default Info;
부모 컴포넌트는 input의 입력값에 따라 onChangeHandler 메서드로 변경된 상태값을 자식에게 넘겨주고 자식 컴포넌트는 이를 props로 받아서 getColorKor와 getMovieGenreKor이라는 함수를 통해 처리한 후에 그 값을 각각 colorKor, movieGenreKor이란 변수에 넣고 이를 리턴문 안에서 출력 합니다
브라우저로 띄워서 보면 이상한 부분이 있는데
바로 이렇게 해당 color만 건드리는데 movie 부분도 불필요하게 렌더링이 되는 문제입니다
지금이야 간단한 로직이니 굳이 최적화를 할 필요가 없지만, 만약에 프로젝트 규모가 엄청나게 크게 된다면 이런 작은 불필요한 렌더링 마저 프로그램을 아주 느리고 무겁게 만들것입니다
사용하는 방법은 간단합니다
import React, { useMemo } from 'react';
... 생략 ...
const colorKor = useMemo(() => getColorKor(color), [color]);
const movieGenreKor = useMemo(() => getMovieGenreKor(movie), [movie]);
이렇게 import를 하고 useMemo안에 콜백으로 함수를 집어넣은 뒤 의존성 배열에 변경되는 상태값을 집어넣으면 됩니다
그러면 이렇게
불필요한 렌더링을 줄일 수 있습니다
이처럼 useMemo는 특정 결과값을 재사용 하는데 사용합니다
근데 함수를 재사용 할때는 무엇을 사용할까요??
다시 전에 쓴 코드로 돌아가봅시다
App.js 를 보면 여기 onChangeHandler 가 있습니다
const onChangeHandler = e => {
if (e.target.id === 'color') setColor(e.target.value);
else setMovie(e.target.value);
console.log('리렌더링');
};
얘는 그냥 입력값을 받아서 상태값을 업데이트 시켜주는 메서드에 불과한데 매번 상태값이 변경될때마다 재선언되고 계속 사용될 필요가 있을까요?
이럴때는 useCallback을 한번 써보세요
useMemo와 마찬가지로 이렇게 useCallback 안에 콜백함수 형태로 로직을 넣어주고 의존성 배열에는 빈배열을 넣어주면 끝 입니다 (맨 처음 한번만 선언시키기 위해서 빈 배열을 집어넣었으나, 함수 안에서 사용되는 상태값이나 props가 있다면 해당 값을 꼭 의존성 배열안에 넣어주자)
const onChangeHandler = useCallback(e => {
if (e.target.id === 'color') setColor(e.target.value);
else setMovie(e.target.value);
console.log('리렌더링');
}, []);
useCallback을 사용함으로써 딱히 눈에 띄는 최적화된 변화는 없습니다
왜냐하면 함수 선언이 메모리를 크게 차지하는 것도 아닐뿐더러 CPU의 리소스도 많이 잡아먹는 작업이 아니기 때문입니다
하지만, 그럼에도 불구하고 우리가 useCallback을 사용하여 최적화를 하려는 이유는 한번 만든 함수를 필요할때만 만들어놓고 재사용하는 개념 자체가 중요하고 또 자식 컴포넌트에게 이 함수를 props로 넘길때 유용하기 때문입니다
props로 함수를 넘길때 유용하다는 말이 무슨 의미일까요??
상위 컴포넌트에서 리렌더링이 되면서 함수가 자꾸 재선언되면 하위 컴포넌트에서는 넘겨받은 함수가 같은 함수임에도 불구하고 달라졌다고 인식을 합니다
컴포넌트는 자신의 state 값이 변경이되거나, 부모에게서 받은 props가 변경이 되거나 할때마다 렌더링을 하게되는데 하위 컴포넌트 입장에서는 props로 내려주는 상위 컴포넌트의 함수가 계속 변경이 되니 같은 함수임에도 불구하고 계속해서 변경이 되었구나 인식을 하게 되는것입니다
다음을 보고 공부하고 정리한 글 입니다
https://www.youtube.com/watch?v=uBmnf_k7_r0
https://react.vlpt.us/basic/18-useCallback.html