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의 주요 특징:
- 재계산 방지: 메모이제이션된 값을 계속 재사용하여, 값이 변하지 않으면 불필요한 계산을 피합니다.
- 성능 최적화: 컴포넌트가 여러 번 리렌더링될 때마다 발생하는 비용이 큰 연산이나 데이터 처리를 최소화할 수 있습니다.
언제 사용하는가?
- 복잡한 연산이 있는 경우, 매번 리렌더링될 때 해당 연산을 다시 실행하는 것을 방지하고 싶을 때
- 렌더링 성능에 영향을 미칠 수 있는 컴포넌트에서 계산된 값이 동일할 경우
- 컴포넌트가 자주 렌더링되면서 불필요한 연산을 줄이고자 할 때
예시: 비싼 연산을 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.memo로 Toast 컴포넌트를 감싸 최적화함
• 이 방식은 렌더링 성능을 개선하고, 토스트 메세지가 자주 발생해도 다른 컴포넌트가 영향을 받지 않도록 함
• 추가로 useMemo의 개념과 사용법을 정리하여 초보자도 쉽게 이해할 수 있도록 구성함
'Dev > Nextjs' 카테고리의 다른 글
[Datepicker] useState의 비동기적 특성으로 상태 업데이트 지연 이슈 (2) | 2024.10.11 |
---|---|
[React-hook-form] controller-checkbox 반복문을 사용한 방식과 개별 작성 방식의 차이 (0) | 2024.10.10 |
🚨[Nextjs] TypeORM 순환참조 Error - TypeORM N+1 - Lazy Loading (0) | 2024.10.01 |
[Nextjs] Nextjs + nprogress 구현 (0) | 2024.09.30 |
[React] React.FC vs React.ButtonHTMLAttributes - 공통컴포넌트 제작방식 (0) | 2024.09.25 |