hmk run dev
useMemo와 useCallback 본문
React를 사용한다는 가정하에
프런트엔드 최적화를 위해 해야 할 것들을 주기적으로 작성해볼 포스팅입니다 :)
useMemo & useCallback
위 두 개의 리액트 훅을 사용하기 전에 이 사실들을 알고넘어갈 필요가 있습니다.
1. 함수형 컴포넌트는 함수다. 단지 jsx를 반환하는 함수입니다.
2. 컴포넌트가 렌더링 된다는 것은 누군가가 그 함수(컴포넌트)를 호출하여 실행된다는 것을 말합니다.
즉, 함수가 실행될 때마다 내부에 선언되어 있던 표현식(변수, 함수 등)도 매번 다시 선언되어 사용됩니다.
3. 컴포넌트는 자신의 상태 값(state)이 변경되거나, 부모에게서 받은 props가 변경될 때마다 리 렌더링이 됩니다.
이렇듯 리액트에선 렌더링이 빈번하게 발생할 수 있으며 렌더링 시 선언해놓은 함수나, 변수 등이 매번 다시 선언되어
불필요한 리소스가 발생할 수 있는데 이러한 낭비를 해결해 줄 수 있는 React Hook이 useMemo와 useCallback이라고 할 수 있습니다.
useMemo
메모이제이션된 값을 반환
다음과 같은 상황이라고 가정해봅시다.
- 하위 컴포넌트는 상위 컴포넌트로 a, b라는 props를 전달받습니다.
- a와 b를 전달받으면 서로 다른 함수로 각각 값을 가공해 새로운 값을 보여주는 역할을 합니다.
- 만약 props로 넘겨받는 a와 b 중 둘 중 하나라도 변경이 감지되면 렌더링이 됩니다.
- 즉, a가 변경되면 b도 같이 변경되어 변경된 건 a인데 변경이 불필요한 b에 자원을 낭비하게 됩니다.
위와 같은 자원낭비 현상을 useMemo를 활용해 해결한 코드를 봅시다.
- App 컴포넌트는 Info컴포넌트로 사용자에게 입력받은 color와 movie를 props로 넘겨줍니다.
- Info 컴포넌트는 넘겨받은 color와 movie를 한글로 바꾸어서 보여줍니다.
// App.js
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;
// Info.js
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;
- App 컴포 너트의 입력창에서 color값만 바꾸어도 getColorKor, getMovieGenreKor 두 함수가 모두 실행됨
- 반대의 경우에도 마찬가지
useMemo를 import후에 아래와 같이 코드를 수정해보자
import React, { useMemo } from "react";
const colorKor = useMemo(() => getColorKor(color), [color]);
const movieGenreKor = useMemo(() => getMovieGenreKor(movie), [movie]);
color값이 바뀔 때는 getColorKor함수만, movie값이 바뀔 때는 getMovieGenreKor함수만 호출되는 것을 확인할 수 있습니다.
지금처럼 재계산하는 함수가 아주 간단하다면 성능상의 차이는 아주 미미하겠지만 만약 재계산하는 로직이 복잡하다면 불필요하게 비싼 계산을 하는 것을 막을 수 있다.
오히려 무분별하게 useMemo를 쓰는 것은 성능상으로 안 좋을 수 있습니다.
props를 넘겨받는 상황이 아닌 하나의 컴포넌트에서 두 개 이상의 state가 있을 때도 활용할 수 있습니다.
useCallback
메모이제이션된 함수를 반환
앞서 말했듯이 컴포넌트가 렌더링 될 때마다 내부에 선언돼있던 함수 표현식과, 변수 등이 매번 다시 선언되어 사용됩니다.
App.js의 onChangeHandler는 onchange(변화 감지)를 할 때마다 재선언이 됩니다.
이러한 불필요한 재선언을 방지하고 첫 마운트 시 한 번만 선언하고 재사용이 되게 하는 방법 중 하나가 useCallback이라고 할 수 있겠습니다.
useCallback을 import하여 onChangeHandler에 적용한 코드입니다.
// App.js
import React, { useState, useCallback } from "react";
const onChangeHandler = useCallback(e => {
if (e.target.id === "color") setColor(e.target.value);
else setMovie(e.target.value);
}, []);
실무에선 주로 이벤트 핸들러 함수나 api요청 함수에 useCallback을 사용하는 코드가 꽤 많습니다.
하지만 useMemo처럼 비싼 계산, 많은 리소스가 투입되는 함수가 아니라면 무분별하게 사용하는 것은 그리좋지 못한
선택입니다.
- React.memo()로 함수형 컴포넌트 자체를 감싸면 넘겨 받는 props가 변경되지 않았을 때는 상위컴포넌트가
메모리제이션된 함수형 컴포넌트를 사용하게 됨
- 함수는 오로지 자기 자신만이 동일하기 때문에 상위 컴포넌트에서 callback 함수를 재선선한다면 props로 callback 함수를 넘겨 받는 하위 컴포넌트 입장에서는 props가 변경 되었다고 인식합니다.
출처
https://leehwarang.github.io/2020/05/02/useMemo&useCallback.html
'React' 카테고리의 다른 글
리액트 성능 측정 & 개선하기 (feat. memo & React DevTools) (0) | 2024.04.20 |
---|---|
Next.js 정적 생성(Static Generation) - getStaticProps, getStaticPaths (0) | 2022.03.01 |
next.js의 SSR (0) | 2022.03.01 |
Next.js의 장점과 프로젝트 구조 (0) | 2022.03.01 |
async, await (0) | 2021.05.20 |