
이전 기본 React Hook에 이어 이번에는 최적화의 관점에서 관련 Hook을 소개해보겠다!
React App 내부의 최적화는 불필요한 작업 줄이는 것에 있음
React에서 리렌더링은 곧 성능(=렌더링 비용)과 직결
리렌더링이 발생하는 상황
useState의 setter 함수로 상태를 변경하면 해당 컴포넌트 리렌더링useContext 로 구독 중인 Context의 값이 변경되면 해당 Context를 사용하는 컴포넌트가 리렌더링불필요한 연산 방지
계산 비용이 큰 연산 결과 값을 메모이제이션 기법으로 캐싱해, 의존성이 변경될 때만 재계산하여 불필요한 연산을 최적화
// 콜백함수, 의존성 배열
useMemo(()=>{},[])
// 예시
const memoizedValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
렌더링 과정에서 의존성 배열에 포함된([a, b]) 값이 이전과 같은지 비교해서, 같으면 저장해둔 결과값을 즉시 반환하고, 다르면 콜백 함수를 다시 실행하여 계산
불필요한 함수 재생성 방지
컴포넌트가 리렌더링 될 때마다 함수가 새롭게 정의되는 것을 방지하고, 이전에 만든 함수를 그대로 재사용
// 최적화하고 싶은 함수, 의존성 배열
useCallback(()=>{},[])
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
useMemo의 경우는 값이 대상이고, useCallback은 함수가 대상
useCallback(fn, deps)는 내부적으로 useMemo(() => fn, deps)와 동일하게 동작
불필요한 리렌더링 방지
컴포넌트를 인수로 받아, props가 바뀌지 않으면 리렌더링을 건너뛰는 최적화된 컴포넌트를 반환
// 반환값: 최적화된 컴포넌트 = memo(컴포넌트)
const MemoizedComponent = memo(Component)
이 Memoized 컴포넌트는 부모 컴포넌트가 리렌더링 되더라도 자신이 받는 props가 바뀌지 않으면(얕은 비교 기준) 다시는 리렌더링이 발생하지 않도록 메모이제이션
두 번째 인수로 사용자 정의 비교 함수 전달 가능
React 가 스스로 props 변경 여부를 판단하는게 아니라,
이전 props와 다음 props를 함수에 넘겨주고 그 반환값으로 판단하도록
export default memo(TodoItem, (prevProps, nextProps)=>{
// 반환값에 따라, Props가 바뀌었는지 안바뀌었는지 판단
// true → props가 바뀌지 않음 → 리렌더링 X
// false → props가 바뀜 → 리렌더링 o
if (prevProps.id !== nextProps.id) return false
if (prevProps.isDone !== nextProps.isDone) return false
if (prevProps.content !== nextProps.content) return false
if (prevProps.date !== nextProps.date) return false
return true; // 여기까지 통과했다면 props가 동일한 것
})
일단은 컴포넌트 구조를 먼저 고려해야한다
관련 없는 state 들을 하나의 컴포넌트에 두지 않고 서로 다른 컴포넌트로 분리하는 것만으로도 리렌더링을 방지할 수 있음
이후 성능 저하가 의심될 때 따져봐야 할 것
useMemo와 useCallback 을 무분별하게 사용하면 위에 나열된 고려 사항으로 인해 더 비용이 들 수 있음
연산 혹은 처리량이 매우 많아서 렌더링의 문제가 되는 경우가 아니라면 단순한 연산이나 의도적인 값 변경에 굳이 쓰지 않는 편이 낫다
따라서, 기능 구현을 완성하고 난 뒤에 최적화가 필요한 지점을 실측으로 판단해본 뒤에 적용하는 것을 추천
// [참고] 자바스크립트의 실행 속도와 성능을 측정하는 도구
console.time() & console.timeEnd()
performance.now()
React Profiler
React 공식문서 내용을 보니 React Compiler가 빌드 타임에 컴포넌트를 정적 분석해서, 지금까지 손으로 작성하던 useMemo, useCallback, React.memo에 해당하는 메모이제이션을 자동으로 해주는 듯 하다
단, 컴파일러가 제어 흐름을 분석하기 어려운 코드 같은 경우는 예외일 수 있으니 컴파일러가 커버하지 못하는 예외 케이스에 집중하여 최적화가 필요한 여지는 남아있는 것으로 보인다
또한, 이 글에서 소개된 hook들은 없어도 코드가 정상 작동해야함을 유의하고 언제나 최적화 관점에서 다가가야 함을 유의하자
Hooks의 종류
한 입 크기로 잘라 먹는 리액트
React의 useMemo와 useCallback을 과도하게 사용하면 안 되는 이유
내가 사용하는 useCallBack, useMemo가 진짜 성능 최적화를 진행중인걸까?
useMemo와 useCallback는 왜, 언제 사용할까?
React - useMemo
React - useCallback
React - memo