[ 일기 컴포넌트를 최적화하기 ]
컴포넌트를 최적화하기 위해서는 어떤 컴포넌트가 최적화의 대상인지 알아내야한다.
1. DiaryEditor 컴포넌트 최적화하기
컴포넌트는 본인이 가진 state의 변화가 생겼거나, 부모 컴포넌트에서 리랜더링이 일어나거나, 전달받은 prop이
변경되는 경우 리랜더링이 일어난다.
DiaryEditor는 onCreate 함수를 prop으로 전달받는다. onCreate는 일기 저장하기 버튼을 눌렀을 때, data에
일기 아이템을 추가하는 역할을 한다. React.memo을 사용해서 DiaryEditor 컴포넌트를 최적화한다.
* React.memo()에 리랜더링되지 않았으면 하는 컴포넌트를 전달해준다면 props가 바뀌지 않는 이상 리랜더링하지
않은 강화된 새로운 컴포넌트를 돌려준다.
컴포넌트에 코드가 많을 경우 직접 React.memo를 적용시키는 것이 아니라 export default 부분을 수정하면 된다.
export default React.memo(DiaryEditor);
DiaryEditor 컴포넌트를 export할 때 React.memo로 묶어준다면 React.memo로 묶인 DiaryEditor를 export
하겠다라는 의미가 되기 때문에 결론적으로는 똑같다.
하지만 DiaryEditor는 결론적으로 2번 랜더링이 된다. 왜냐하면 App 컴포넌트의 data state가 빈 배열인 상태로 한번
랜더링이 되고, 컴포넌트가 mount되는 시점에 호출한 getData()를 통해 얻어온 데이터를 setData 상태 변화 함수를
통해 data state가 한번 더 변하게 되면서 App 컴포넌트가 mount되면서 총 2번 랜더링이 되기 때문이다.
그렇기 때문에 App 컴포넌트로부터 DiaryEditor 컴포넌트로 prop으로 전달받은 onCreate 함수도 App 컴포넌트가
리랜더링이 되면서 같이 다시 랜더링이 되게 된다. 비원시타입 자료형의 비교는 기본적으로 얕은 비교이기
때문에 DiaryEditor에서 prop으로 전달받은 onCreate 함수가 App 컴포넌트가 랜더링이 될 때마다 다시 만들어져서 DiaryEditor도 2번 랜더링이 된다.
onCreate 함수는 App 컴포넌트가 재생성될 때마다 계속 생성이 되기 때문에 일기 데이터가 삭제, 수정이 되면
DiaryEditor는 계속 랜더링이 된다. 결론적으로 DiaryEditor가 부모 컴포넌트로 인해 재생성되는 것을 막기 위해서는
onCreate 함수가 재생성되는 것을 막아야 DiaryEditor를 React.memo로 최적화할 수 있다.
2. App 컴포넌트에서 onCreate 함수가 재생성되지 않도록 만들어준다.
useMemo는 함수가 반환하는 값을 다시 사용할 수 있도록 dependency array를 기준으로 memoization을 도와준다.
useMemo는 결론적으로 함수를 반환하는 것이 아니라 값을 반환하기 때문에 함수를 원본 그대로 DiaryEditor에게
전달해주기 위해서는 사용하면 안된다.
[ useCallback ]
useCallback의 기능은 memoization된 콜백을 반환한다. 즉, useMemo처럼 값을 반환하는 것이 아니라
memoization된 콜백함수를 다시 반환해주는 역할을 한다.
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useCallback은 첫번째 인자로 콜백함수를 , 두번째 인자로 dependency array를 전달받는다.
dependency array안에 들어있는 값이 변환하지 않으면 첫번째 인자로 전달한 콜백함수를 계속 재사용할 수 있도록
도와준다.
[ useCallback을 이용해서 onCreate 함수가 다시 생성되지 않도록 하기 ]
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
const onCreate = useCallback((author, content, emotion) => {
// 일기 데이터 추가 함수
const created_date = new Date().getTime();
const newItem = {
author,
content,
emotion,
created_date,
id: dataId.current,
};
dataId.current += 1;
setData([newItem, ...data]); // 기존에 존재하던 데이터에 새로운 일기를 추가
}, []);
useCallback의 첫번째 인자로 전달하는 콜백함수는 DiaryEditor가 작성 완료 버튼을 눌렀을 때 데이터를 추가하는
함수가 되고, 두번째 인자에는 빈 배열을 전달해서 mount 되는 순간에만 첫번째로 랜더가 되고, 그 다음부터는
첫번째로 랜더했던 함수가 재사용이 될 수 있도록 만들어준다.
useCallback이 잘 작동을 한다면 일기 data 한개를 삭제할 때 onCreate가 재사용되지 않아, DiaryEditor는
리랜더링되지 않는다.
하지만 onCreate 함수를 통해서 일기 데이터를 하나 더 추가할 경우 기존에 있던 모든 일기 데이터가 사라지고
새로 추가된 일기 하나만 존재하게 된다. 그 이유는 useCallback의 dependency array로 빈 배열을 전달해줬기
때문이다. onCreate 함수는 컴포넌트가 mount 되는 순간 딱 한번만 생성이 되는데, 그 당시에 data state는
빈 배열이다. onCreate 함수가 가장 마지막에 생성됬을 때의 data는 빈 배열이기 때문에 발생하게 된다.
함수는 컴포넌트가 재생성될 때 다시 생성되는 이유가 존재한다. 그 이유가 바로 현재 state를 참조할 수 있어야
하기 때문이다. onCreate 함수는 콜백 안에 갇혀서 dependency array에 빈 배열을 전달했기 때문에 onCreate
함수가 알고 있는 data의 값은 빈 배열이다. 그래서 setData 상태 변화함수를 통해 빈 배열에 새로 추가하는
일기 데이터인 newItem을 전달해줘서 최종적으로는 새로 추가한 1개의 일기 데이터만 보이게 되는 것이다.
정상적으로 작동을 하기 위해서는 dependency array에 data state를 넣어줘야 한다. dependency array는
안에 있는 값이 변경되면 onCreate 함수를 재생성하게 된다. 우리는 data state가 변화한다해서
onCreate 함수가 재생성되지 않도록 구현하는 것이 목표이다. 하지만 onCreate 함수가 재생성하지 않으면
최신의 data state 값을 참조할 수 없기 때문에 굉장히 이상한 동작을 하게 된다.
이러한 상황에서는 함수형 업데이트를 활용한다.
지금까지는 상태 변화 함수의 인자로 새로운 state의 값을 전달했지만 함수형 업데이트는 상태 변화 함수의
인자로 함수를 전달하게 된다.
const onCreate = useCallback((author, content, emotion) => {
// 일기 데이터 추가 함수
const created_date = new Date().getTime();
const newItem = {
author,
content,
emotion,
created_date,
id: dataId.current,
};
dataId.current += 1;
setData((data)=>[newItem, ...data]); // 기존에 존재하던 데이터에 새로운 일기를 추가
}, []);
setData((data)=>[newItem, ...data]);
화살표 함수를 사용하고 인자로 data를 받아서 새로운 아이템을 추가한 data를 리턴하는 콜백 함수를 setData
함수에 전달해준다.
상태 변화 함수에 인자로 함수를 전달하는 것을 함수형 업데이트라 한다. 함수형 업데이트의 경우 dependency
array에 빈 배열을 전달해줘도 항상 최신의 state를 상태 변화 함수의 콜백 함수로 전달되는 인자로 인해 참고할 수
있게 된다.
즉 이전에는 onCreate 함수가 data state의 현재 값을 참조할 수 없었기 때문에 아이템이 한개만 남아있었더라면
setData 함수에 콜백 함수를 전달해준다면 정상적으로 onCreate가 작동하는 것을 확인할 수 있다.
또한 DiaryEditor가 처음 실행할 경우 두 번 재생성이 되어 두 번 리랜더링이 되었지만 useCallback을 통해서
한번만 랜더링이 되는 것을 확인할 수 있다. 일기 아이템을 하나 삭제한다하더라도 DiaryEditor는 재생성되지
않는다.
'Front-End > React' 카테고리의 다른 글
[ React ] React 기본 ⑧ 복잡한 상태 관리 로직 분리하기 - useReducer (0) | 2022.10.22 |
---|---|
[ React ] React 기본 ⑦ 최적화 - 최적화 완성 (0) | 2022.10.22 |
[ React ] React 기본 ⑦ 최적화 - React.memo (0) | 2022.10.22 |
[ React ] React 기본 ⑦ 최적화 - useMemo (0) | 2022.10.15 |
[ React ] React 기본 ⑥ React Developer Tools (0) | 2022.10.15 |