또다른 최적화 방법 useMemo
불필요한 재평가 과정 피하기
예시 App.js
function App() {
const [listTitle, setListTitle] = useState("My List");
const changeTitleHandler = useCallback(() => {
setListTitle("New Title");
}, []);
return (
<div className="app">
<DemoList title={listTitle} items={[5, 3, 1, 10, 9]} />
<Button onClick={changeTitleHandler}>Change List Title</Button>
</div>
);
}
여기서 changeTitleHandler 는 useCallback으로 함수를 저장해 두고 있고,
Button 컴포넌트는 React.memo로 props가 실제로 바뀌었을 때만 렌더링 하도록
최적화를 한 상태다.
DemoList는 items라는 배열을 props로 전달받아 sort를 하여 목록을 순서대로 나열하고 있다.
const DemoList = props => {
const sortedList = props.items.sort((a, b) => a - b);
return (
<div className={classes.list}>
<h2>{props.title}</h2>
<ul>
{sortedList.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
};
export default DemoList
그래서 props로 받은 배열은 정렬되어있지 않은 상태지만 화면에서 보다시피
정렬된 상태로 나타나 있다.
그런데 이 sort방식은 성능에 영향을 많이 미칠 수 있다.
전체 컴포넌트가 재평가 될 때마다 이것을 실행하게 되니까 말이다.
그러면 export하는 곳에 아래와 같이 React.memo를 하면 되지 않을까?
export default React.memo(DemoList)
Button을 클릭하면 Title이 바뀌게 되어있다.
그런데 여기서 DemoList 컴포넌트에 console.log로 'DemoList RUNNING'을 찍어보면
버튼을 클릭해도 콘솔로그가 계속 'DemoList RUNNING'이 찍히는 것을 알 수 있다.
props의 일부 (props.title)가 바뀌었기 때문에 당연히 실행되는 것이다.
React.memo는 정상 작동 하는 것이다.
setListTitle로 App컴포넌트가 재렌더링 되었으니 항상 다시 실행된다.
<DemoList title={listTitle} items={[5, 3, 1, 10, 9]} />
items의 배열은 부모 컴포넌트가 재실행 될 때마다
재생성된다.
title이 바뀌지 않아도 부모 컴포넌트가 다른 이유로 재실행 되면
DemoList컴포넌트는 items배열이 재생성 되어 다시 실행된다.
(함수와 마찬가지로 배열도 객체, 객체는 참조값, 같은 배열도 같은것이 아님
메모리에서 다른 위치를 차지하고 있으므로 )
컴포넌트를 재실행 할 때 재평가 하고 싶지 않을 때가 있다.
성능이 저하되는 경우 더더욱.
useCallback은 함수를 저장하여 dependency가 변경될 때만 리빌드 하게 해주었다.
비슷하게 useMemo는 모든 종류의 데이터를 저장할 수 있다.
const DemoList = props => {
const sortedList = useMemo(() => {
return props.items.sort((a, b) => a - b);
}, []);
이렇게 하면 sortedList의 정렬 결과를 기억할 수 있게 한다.
dependencies의 변경사항이 생길 때마다 업데이트 된다.
item이 추가될 때 리빌드 하기 위해
다음과 같이 코드를 수정한다.
const DemoList = props => {
const { items } = props;
const sortedList = useMemo(() => {
items.sort((a, b) => a - b);
}, [items]);
그 뒤 다시 실행해보면 여전히
DemoList RUNNING이 console.log에 출력되는 것을 알 수 있다.
왜 그런가?
<DemoList title={listTitle} items={[5, 3, 1, 10, 9]} />
여전히 여기의 props로 주고있는 items가 변경된다.
불필요한 재렌더링을 막기 위해 여기서도 useMemo를 사용할 수 있다.
<DemoList title={listTitle} items={useMemo(()=> [5, 3, 1, 10, 9], [])} />
dependencies에는 빈 배열을 넣어준다.
그러면 처음 렌더링 시에만 실행된다.
useMemo는 useCallback에 비해 많이 사용되지 않는다.
데이터 재계산과 같은 성능문제 때문에 데이터를 저장해야 할 필요도 있지만
기본적으로 함수를 저장하는 일이 더 많이 필요할 것이기 때문이다.
데이터 저장도 성능을 사용하는 것이다.
그러므로 모든 값을 다 useMemo하는 것은 아니고,
뭔가 정렬할 때는 불필요한 정렬을 막을 수 있기 때문에
사용하는 것이 좋다.