본문 바로가기

[Nextjs]useMemo 를 이용하여 불필요한 리렌더링 문제 해결

@Jeeqong 2024. 10. 5. 14:45
반응형

1. 이슈

1.1. 발생 환경 : 

Task App 
- Task, Label add, edit Dialog popup 으로  구성

- 대부분의 경고메시지는 Toast 를 Layout 에 선언해 전역적으로 사용 중


1.2. 발생 상황 

Task Edit Dialog 에서 Submit 버튼을 클릭시 

변경된 컨텐츠가 없어서 Toast 메세지를 띄우고 로직 중단되었는데 
바닥Page 에서 rerendering 이 불필요하게 일어나는 상황


1.3. 해결방안 useMemo 및 React.memo 를 활용하여 ToastProvider 최적화

useMemo를 사용하여 ToastProvider에서 showToast 함수가 메모이제이션되도록 처리하면, ToastProvider 컴포넌트가 리렌더링될 때마다 새로운 함수가 생성되는 것을 방지할 수 있다. 이거슨 리렌더링 횟수를 줄이고, 컴포넌트가 불필요하게 다시 그려지는 것을 방지해 성능을 최적화할 수 있다.

 

수정전 : 

// src/components/common/Toast/ToastContext.tsx
export const ToastProvider = ({ children }: { children: React.ReactNode }) => {
	const [toasts, setToasts] = useState<Array<{ id: number, message: string, type: ToastTypeProps, position: ToastProps['position'] }>>([]);

	const showToast = (message: string, type: ToastTypeProps, position: ToastProps['position'] = 'top-right') => {
		const newToast = { id: Date.now(), message, type, position };
		setToasts(prev => [newToast, ...prev]);

		setTimeout(() => {
			setToasts(prev => prev.filter(toast => toast.id !== newToast.id));
		}, 3000);
	};

	return (
		<ToastContext.Provider value={{ showToast }}>
			{children}
			{toasts.map(toast => (
				<Toast
					key={toast.id}
					{...toast}
					onClose={() =>
					setToasts((prev) => prev.filter((t) => t.id !== toast.id))
				}/>
			))}
		</ToastContext.Provider>
	)
}

 

수정후 : 

const showToast = useMemo (
    () => (
        message: string,
        type: ToastTypeProps,
        position: ToastProps['position'] = 'top-right'
    ) => {
        const newToast = { id: Date.now(), message, type, position }
        setToasts((prev) => [newToast, ...prev]) // 새 토스트가 앞에 쌓이도록

        setTimeout(() => {
            setToasts((prev) => prev.filter((toast) => toast.id !== newToast.id))
        }, 3000)
    },
    []
)

 

수정전 : 

//Toast.tsx
const Toast = ({
	message,
	type,
	onClose,
	duration = 3000,
	position = 'top-right',
}: ToastProps) => {
	중략...
}

 

수정후 : 

const Toast = React.memo(({
	message,
	type,
	onClose,
	duration = 3000,
	position = 'top-right'
}: ToastProps) => {
	중략...
})

 


2. useMemo 란?

useMemo는 React에서 제공하는 성능 최적화 도구로, 계산 비용이 높은 작업을 메모이제이션하여 불필요한 재계산을 방지하는 데 사용됩니다. 특정 값이 변경되지 않으면 이전 계산 결과를 재사용하는 방식으로 작동합니다.


2.1. 기본 사용법 : 

const memoizedValue = useMemo(() => {
  return expensiveFunction();
}, [dependency1, dependency2]);

2.2. 매개변수:

  • 첫 번째 인자로는 함수가 들어갑니다. 이 함수는 컴포넌트가 렌더링될 때마다 실행되지 않고, 의존성 배열(dependency array)에 포함된 값들이 변경될 때만 실행됩니다.
  • 두 번째 인자는 의존성 배열입니다. 배열 안에 있는 값들 중 하나라도 변경되면 첫 번째 함수가 다시 실행되며, 해당 값이 변경되지 않으면 이전에 계산된 값을 그대로 사용합니다.

2.3. useMemo의 주요 특징:

  1. 재계산 방지: 메모이제이션된 값을 계속 재사용하여, 값이 변하지 않으면 불필요한 계산을 피합니다.
  2. 성능 최적화: 컴포넌트가 여러 번 리렌더링될 때마다 발생하는 비용이 큰 연산이나 데이터 처리를 최소화할 수 있습니다.

언제 사용하는가?

  • 복잡한 연산이 있는 경우, 매번 리렌더링될 때 해당 연산을 다시 실행하는 것을 방지하고 싶을 때
  • 렌더링 성능에 영향을 미칠 수 있는 컴포넌트에서 계산된 값이 동일할 경우
  • 컴포넌트가 자주 렌더링되면서 불필요한 연산을 줄이고자 할 때

예시: 비싼 연산을 useMemo로 최적화

const MyComponent = () => {
  const [count, setCount] = useState(0);

  // 이 함수는 count가 바뀔 때만 실행된다.
  const expensiveCalculation = useMemo(() => {
    console.log('비싼 계산 실행');
    return count * 2;  // 예시로 간단한 연산
  }, [count]);

  return (
    <div>
      <p>계산 결과: {expensiveCalculation}</p>
      <button onClick={() => setCount(count + 1)}>증가</button>
    </div>
  );
}

 

예시는 count가 변경될 때만 expensiveCalculation이 다시 계산되고, 그 외에는 이전 계산된 값이 재사용됩니다. 이로 인해 useMemo는 count가 변하지 않으면 불필요한 연산을 하지 않게 해줍니다.


✅ 결론 요약

Toast 컴포넌트를 전역적으로 관리하면서, 불필요한 리렌더링 이슈가 발생함

특히 변경된 내용 없이 Submit 시 Toast만 띄우는 경우에도 하위 페이지 전체가 리렌더링되는 현상이 문제였음

이를 해결하기 위해 useMemo를 활용해 showToast 함수를 메모이제이션하고, React.memoToast 컴포넌트를 감싸 최적화함

이 방식은 렌더링 성능을 개선하고, 토스트 메세지가 자주 발생해도 다른 컴포넌트가 영향을 받지 않도록 함

추가로 useMemo의 개념과 사용법을 정리하여 초보자도 쉽게 이해할 수 있도록 구성함

 

 

반응형
Jeeqong
@Jeeqong :: JQVAULT

Jeeqong's vault : 정보/기록을 쌓아두는 공간 웹개발 포스팅 일상 리뷰를 기록하는 공간입니다.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차