useMemo와 useCallback을 배우기 전 알야아 한 것
1. 함수형 컴포넌트는 그냥 함수다. 함수형 컴포넌트는 단지 jsx를 변환하는 함수
2. 컴포넌트가 렌더링 된다는 것은 누군가 그 함수(컴포넌트)를 호출해 실행되는 것을 뜻한다.
함수가 실행될 때마다 내부에 선언되어 있던 표현식(변수, 또는 다른 함수 등) 도 매번 다시
선언되어 사용된다.
3. 컴포넌트는 자신의 state가 변경되거나, 부모에게서 받은 props가 변경될 때마다 리렌더링된다.
(심지어 하위 컴포넌트에 최적화 설정을 해주지 않으면 부모에게서 받은 props가 변경되지 않더라도
리렌더링 되는게 기본)
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a,b), [a,b]);
메모이제이션 된 값을 반환한다라는 문장이 핵심이다. 다음과 같은 상황을 상상해보면 하위 컴포넌트는
상위 컴포넌트로부터 a와 b라는 두 개의 props를 전달 받는다. 하위 컴포넌트에서는 a와 b를 전달받으면 서로 다른
함수로 각각의 값을 가공(또는 계산)한 새로운 값을 보여주는 역할을 한다.
하위 컴포넌트는 props로 넘겨 받는 인자가 하나라도 변경될 떄마다 렌더링되는데,
props.a만 변경되었을때 이전과 같은 값인 props.b도 다시 함수를 호출해 계산해야 할까?
useMemo를 설명할 수 있는 간단한 예제를 살펴보겠다.
App.js는 info컴포넌트에게 사용자로부터 입력받은 color와 movie값을 props로 넘겨주고,
info 컴포넌트는 전달 받은 color와 movie를 적절한 한글로 바꾸어 문장으로 보여준다.
// App.js
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" vlaue={color} onChange={onChangeHandler} />
</label>
</div>
<div>
What is your favorite movie among these?
<label>
<input
type="radio"
name="movie"
value="Marriage Story"
onChange={onChangeHandler}
/>
Marrige 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>
<Info color={color} movie={movie} />
</div>
</div>
);
}
export default App;
// Info.js
const getColorKor = color => {
console.log("geColorKor");
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 두 함수 모두 실행되고, movie값만 바꿔도 마찬가지로 두 함수 모두 실행된다.
useMemo를 import해서 info 컴포넌트의 코드에서 colorKor과 movieGenreKor를 계산하는 부분을
아래와 같이 수정해 볼 수 있다.
import React, { useMemo } from 'react';
const colortKor = useMemo(() => getColorKor(color, [color]);
const movieGenreKor = useMemo(() => getMovieGenreKor(movie), [movie]);
useMemo를 사용하면 의존성 배열에 넘겨준 값이 변경될때만 메모이제이션 된 값을 다시 계산한다.
예제 코드를 직접 변경해 color 값이 바뀔 때는 getColorKor 함수만, movie 값이 바뀔 때는 getMovieGenreKor함수만
호출되는 것을 확인 할 수 있다
재계산하는 함수가 아주 간단하다면 성능상의 차이는 아주 미미하겠지만, 만약 재계산 하는 로직이 복잡하다면
불필요하게 비싼 계산을 하는 것을 막을 수 있다.
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a,b);
},
[a,b];
);
메모이제이션된 함수를 반환한다는 문장이 핵심이다.
컴포넌트가 렌더링될때마다 내부에 선언되어 있던 표현식(변수, 또다른 함수 등)도 매번 다시 선언되어 사용된다.
useMemo를 설명하고 있는 예제에서 App.js의 onChangeHandler함수는 내부의 color,movie 상태값이 변경될 때마다
재선언된다는 것을 의미한다. 하지만 onChangeHandler함수는 파라미터로 전달받은 이벤트 객체(e)의 target.id 값에 따라 setState를 실행해주기만 하면 되기 때문에, 첫 마운드 될때 한 번만 선언하고 재사용하면 되지 않을까?
// 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)
}, []);
App.js에서 useCallback을 import하고 onChangehandler함수의 선언부를 위처럼 바꿔보았다.
첫 마운트 될 때만 메모리에 할당되었는지 아닌지 확인하기는 어려우나 위처럼 사용한다.
만약, 하위 컴포넌트가 React.memo() 같은 것으로 최적화 되어 있고 그 하위 컴포넌트에게
callback으로 함수를 선언하는 것이 유용하다. 함수가 매번 재선언되면 하위 컴포넌트는
넘겨 받은 함수가 달라졌다고 인식하기 때문이다.
- React.memo()로 함수형 컴포넌트 자체를 감싸게 되면 넘겨 받은 props가 변경되지 않았을 때는
상위 컴포넌트가 메모이제이션된 함수형 컴포넌트(이전에 렌더링된 결과)를 사용하게 된다. - 함수는 오로지 자기 자신만이 동일하기 때문에 상위 컴포넌트에서 callback 함수를
(같은 함수더라도) 재선언한다면 props로 callback함수를 넘겨 받는 하위 컴포넌트 입장에서
props가 변경되었다고 인식한다.
참고
'프론트 엔드 > React' 카테고리의 다른 글
[React] 컴포넌트 라이프 사이클과 주요 메서드 호출 순서 (0) | 2021.10.14 |
---|---|
[React] 리덕스 (Redux)란 ? (0) | 2021.10.12 |
[React]setState는 비동기 동작 (0) | 2021.10.12 |