Posts 성능하면 빠질 수 없는 메모이제이션, 네가 궁금해
Post
Cancel

성능하면 빠질 수 없는 메모이제이션, 네가 궁금해

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를 사용할 수 있다.
    • 의존성이 객체 또는 함수여서 의도하지 않게 너무 자주 변경되는 경우
This post is licensed under CC BY 4.0 by the author.

리액트로 인해 잊혀진 것들

Lazy

Comments powered by Disqus.