https://d2.naver.com/helloworld/9223303
메모이제이션으로 성능을 높일 수 있으나, 메모이제이션은 메모리에 특정한 값을 저장하는 것이기에 정말 필요하지 않는 경우에도 남용하면 성능 저하의 원인이 된다.
React에서의 메모이제이션 사용
React 에서는 memo
, useMemo
, useCallback
을 통해 메모이제이션을 사용할 수 있다. 보통 컴포넌트 내 코드는 리렌더링마다 재실행되지만, 메모이제이션 된 경우 의존성이 변화하지 않았다면 재실행 되지 않는다.
메모이제이션과 성능
모든 라인에 있는 코드는 실행될 때 비용을 수반한다.
1
2
3
const done = React.useCallback((todo) => {
setTodos((prevTodos) => prevTodos.filter((t) => t !== todo));
}, []);
위처럼 메모이제이션을 할 경우 done
함수를 그냥 선언했을때보다 많은 일을 하게 된다. 일반함수는 가비지 컬렉션 되고 새로운 함수를 생성하는 단순한 작업을 하는데, useCallbak
을 사용하면 의존성 확인을 위해 종속된 값들의 참조를 가지고 있어야하며 매 렌더링마다 해당 값들을 비교해야한다. 성능을 높이기 위해 useMemo
, useCallback
을 무분별하게 사용하면 오히려 성능이 떨어지는 결과를 가져온다.
메모이제이션이 필요한 경우
- 연산 혹은 처리량이 매우 많은 계산을 할때
- 자식 컴포넌트에 함수를 props로 넘길 때
- 함수는 매번 재생성되므로, 함수를 자식으로 넘길때 함수가 변경되지 않았을 때도 자식은 리렌더링 된다.
- 이떄
useCallback
으로 함수를 메모이제이션하고, 자식의 리렌더링을 막을 수 있다.
- 다른 훅의 의존성에 사용될 때
- 예를들어
useEffect
의 의존성 목록에 비용이 많이 드는 계산이나 렌더링 간에 변경할 필요가 없는 개체가 있을 때 메모이제이션을 사용할 수 있다. - 콜백 함수 등을 의존성에 포함해야할때 메모이제이션을 사용하면 콜백 함수 참조가 안정적으로 유지되고 의존성이 변경될 때 불필요한 Effect가 트리거 되지 않는다.
- 예를들어
메모이제이션이 필요하지 않는 경우
- 의존성이 자주 변경되는 경우
- 오히려 매번 비교하는 비용이 더 클 수 있다.
- 작은 컴포넌트
- 기본적이거나 빠른 작업
- 기본 산술, 문자열 연결, 배열 조작 같은 간단한 작업은 일반적으로 빠르기에 메모이제이션이 필요하지 않다.
- 연산이 복잡하지 않은 함수에 메모이제이션을 사용하는 것은 메모리 낭비이므로 간단한 함수에는 메모이제이션을 사용하지 않는 것이 좋다.
불필요한 메모이제이션을 제거하는 원칙
- 래퍼 컴포넌트에서 state가 변경되어도 자식 컴포넌트는 리렌더링 되지 않는다.
되도록 로컬 state를 사용하고, state를 상위나 전역으로 올리지마라 ```jsx export default function Accordion() { const [activeIndex, setActiveIndex] = useState(0);
return ( <> <h2>Almaty, Kazakhstan</h2> <Panel title=”About” isActive={activeIndex === 0} onShow={() => setActiveIndex(0)} > … </Panel> <Panel title=”Etymology” isActive={activeIndex === 1} onShow={() => setActiveIndex(1)} > … </Panel> </> ); }
function Panel({ title, children, isActive, onShow }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={onShow}> Show </button> ) } </section> ); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- 위와 같은 구조는 `Accordion`에서 `activeIndex`가 변경시 모든 `Panel`이 리렌더링되기에 좋지 않다. 다음과 같이 리팩토링할 수 있다.
```jsx
export default function Accordion() {
return (
<>
<h2>Almaty, Kazakhstan</h2>
<Panel title="About">
...
</Panel>
<Panel title="Etymology">
...
</Panel>
</>
);
}
function Panel({ title, children }) {
const [isActive, setIsActive] = useState(false);
function onShow() {
setIsActive(prev => !prev)
}
return (
<section className="panel">
<h3>{title}</h3>
{isActive ? ( <p>{children}</p> ) :
( <button onClick={onShow}> Show </button> )
}
</section>
);
}
- 렌더링 논리를 순수하게 유지한다.
- 리렌더링 했을 때 컴포넌트의 버그로 문제가 발생하여 메모이제이션을 하는 경우가 있는데, 이때는 로직이 잘못된 것이기에 로직을 수정해야한다.
- state를 업데이트하는 불필요한 Effect를 피한다 참고
- 렌더링을 위해 데이터를 변환하는 경우
- 사용자 이벤트를 처리하는 경우
- Effect의 불필요한 의존성을 제거한다
- 조건에 따라 Effect 안의 다른 부분을 재실행하고 싶은 경우에는 조건이 변경될 시 실행하고 싶지 않은 부분도 실행되므로 다른 Effect로 분리하는게 좋다.
- 변화에 반응하는 대신 의존성의 최신 값을 읽기만 하고 싶은 경우,
useRef
를 사용하거나useEventEffect
를 사용할 수 있다. - 의존성이 객체 또는 함수여서 의도하지 않게 너무 자주 변경되는 경우