React Hooks - 최적화(React.memo, useCallback, useMemo)
(1) 리-렌더링의 발생 조건
- 컴포넌트에서 state가 바뀌었을 때
- 컴포넌트가 내려받은 props가 변경되었을 때
- 부모 컴포넌트가 리-렌더링 된 경우 자식 컴포넌트는 모두
(2) 최적화
리액트에서 리렌더링이 빈번하게, 자주 일어난다는 것은 그렇게 좋은 소식은 아니ek. 비용이 발생하는 것은 최대한 줄여야 한다. 이런 작업을 우리는 최적화(Optimization).
리액트에서 불필요한 렌더링이 발생하지 않도록 최적화하는 대표적인 방법은
- memo(React.memo) : 컴포넌트를 캐싱
- useCallback : 함수를 캐싱
- useMemo : 값을 캐싱
memo(React.memo)
: 리-렌더링의 발생 조건 중 3번째 경우. 즉, 부모 컴포넌트가 리렌더링 되면 자식컴포넌트는 모두 리렌더링 된다.
부분을 돕는 도구가 바로 React.memo!!
컴포넌트를 메모리에 저장해두고 필요할 때 갖다 쓰게 됩니다. 이렇게 하면 부모 컴포넌트의 state의 변경으로 인해 props가 변경이 일어나지 않는 한 컴포넌트는 리렌더링 되지 않아요. 이것을 컴포넌트 memoization 이라고 한다.
export default React.memo(Box1);
export default React.memo(Box2);
export default React.memo(Box3);
useCallback
: 인자로 들어오는 함수 자체를 기억(메모이제이션)
?? : React.memo를 통해서 Box1.jsx는 메모이제이션을 했는데도 리렌더링이 되네요?
네, 그것은 우리가 함수형 컴포넌트를 사용하기 때문이고 App.jsx가 리렌더링 되면서 코드가 다시 만들어지기 때문이다.
자바스크립트에서는 함수도 객체의 한 종류에요. 따라서 모양은 같더라도 다시 만들어지면 그 주솟값이 달라지고 이에 따라 하위 컴포넌트인 Box1.jsx는 props가 변경됐다고 인식한다.
const onInitButtonClickHandler = () => {
initCount();
};
이 함수를 메모리 공간에 저장해놓고, 특정 조건이 아닌 경우엔 변경되지 않도록 하려면?
// 변경 후
const initCount = useCallback(() => {
setCount(0);
}, []);
count를 초기화 할 때, 콘솔을 찍으려멱?
...
// count를 초기화해주는 함수
const initCount = useCallback(() => {
console.log(`[COUNT 변경] ${count}에서 0으로 변경되었습니다.`);
setCount(0);
}, []);
...
: [COUNT 변경] 0에서 0으로 변경되었습니다.
이런 현상이 발생하는 이유는, useCallback이 count가 0일 때의 시점을 기준으로 메모리에 함수를 저장했기 때문이다. 이 때문에 우리는 dependency array가 필요하다.
기존 코드의 dependency array에 count를 넣으면, count가 변경 될 때 마다 새롭게 함수를 할당한다.
...
// count를 초기화해주는 함수
const initCount = useCallback(() => {
console.log(`[COUNT 변경] ${count}에서 0으로 변경되었습니다.`);
setCount(0);
}, [count]);
...
useMemo
: 여기서 말하는 memo는 memoization을 뜻한다. 기억한다는 말이다.
동일한 값을 반환하는 함수를 계속 호출해야 하면 필요없는 렌더링을 한다고 볼 수 있다. 맨 처음 해당 값을 반환할 때 그 값을 특별한 곳(메모리)에 저장한다.. 이렇게 하면 필요할 때 마다 다시 함수를 호출해서 계산하는게 아니라 이미 저장한 값을 단순히 꺼내와서 쓸 수 있다. 보통 이러한 기법을 캐싱을 한다. 라고 표현한다.
사용방법
dependency Array의 값이 변경 될 때만 **반환할_함수()**가 호출된다.
그 외의 경우에는 memoization 해놨던 값을 가져오기만 한다.
// as-is
const value = 반환할_함수();
// to-be
const value = useMemo(()=> {
return 반환할_함수()
}, [dependencyArray]);
const me = useMemo(() => {
return {
name: "Ted Chang",
age: 21,
isAlive: isAlive ? "생존" : "사망",
};
}, [isAlive]);
~~
~~ 나중에 적기
useMemo()만 이렇게 써주면, uselessCount가 아무리 증가돼도 영향이 없게 된다.
주의해야 할 사항
useMemo를 남발하게 되면 별도의 메모리 확보를 너무나 많이 하게 되기 때문에 오히려 성능이 악화될 수 있다.
필요할 때만 쓰기😎