반응형
메모이제이션
불필요한 리렌더링을 방지하는 기법
React.memo
컴포넌트를 메모이제이션
props가 변경되지 않으면 컴포넌트를 리렌더링 하지 않습니다. 하지만 props가 객체나 함수인 경우 주의가 필요합니다.
챗토리에선 이렇게 사용했어요.
// MessageItem 컴포넌트 메모이제이션
const MessageItem = React.memo(({ message, onDelete }) => {
return (
<div>
{message.content}
<button onClick={() => onDelete(message.id)}>삭제</button>
</div>
);
});
useMemo
계산 비용이 큰 값을 메모이제이션
// MessageList.tsx에서 메시지 그룹화 로직 최적화
const MessageList = () => {
const messages = useChatStore((state) => state.messages);
// 메시지 그룹화 로직 메모이제이션
const messageGroups = useMemo(() => {
console.log('메시지 그룹화 계산 실행');
const groups = [];
// ... 복잡한 그룹화 로직 ...
return groups;
}, [messages]); // messages가 변경될 때만 재계산
return (
<div>
{messageGroups.map(group => (
// 그룹 렌더링
))}
</div>
);
};
useCallback
함수를 메모이제이션
// MessageInput.tsx에서 최적화
const MessageInput = () => {
const [input, setInput] = useState('');
const addMessage = useChatStore((state) => state.addMessage);
const isBotTyping = useChatStore((state) => state.isBotTyping);
const chatMutation = useChatQuery();
// handleSend 함수 메모이제이션
const handleSend = useCallback(() => {
if(!input.trim() || isBotTyping){
return;
}
addMessage('user', input);
chatMutation.mutate([{ role: "user", content: input }]);
setInput('');
}, [input, isBotTyping, addMessage, chatMutation, setInput]);
// 키 이벤트 핸들러 메모이제이션
const handleKeyDown = useCallback((e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSend();
}
}, [handleSend]);
return (
<div className="flex flex-col">
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown} // 메모이제이션된 함수 사용
// ...
/>
<Button onClick={handleSend}>
<HiPaperAirplane className="h-5 w-5" />
</Button>
</div>
);
};
가상화(Virtualization)
// react-window 사용 예시
import { FixedSizeList as List } from 'react-window';
const MessageList = () => {
const messages = useChatStore((state) => state.messages);
// 각 메시지 항목 렌더링 함수
const Row = ({ index, style }) => {
const message = messages[index];
return (
<div style={style}>
{/* 메시지 내용 */}
</div>
);
};
return (
<List
height={500} // 목록 높이
itemCount={messages.length} // 항목 수
itemSize={100} // 각 항목 높이
width="100%" // 목록 너비
>
{Row}
</List>
);
};
코드 분할(Code Splitting)
// React.lazy와 Suspense 사용
import React, { Suspense, lazy } from 'react';
// 컴포넌트 지연 로딩
const ChatContainer = lazy(() => import('./components/chat/ChatContainer'));
const Settings = lazy(() => import('./components/Settings'));
const App = () => {
const [currentPage, setCurrentPage] = useState('chat');
return (
<div>
<nav>
<button onClick={() => setCurrentPage('chat')}>채팅</button>
<button onClick={() => setCurrentPage('settings')}>설정</button>
</nav>
<Suspense fallback={<div>로딩 중...</div>}>
{currentPage === 'chat' ? <ChatContainer /> : <Settings />}
</Suspense>
</div>
);
};
불변성(Immutability) 유지
리액트는 얕은 비교를 통해 상태 변화를 감지하므로, 상태를 변경할 때 불변성을 유지하는 것이 중요합니다
// 잘못된 방법 (불변성 위반)
const addMessage = (role, content) => {
const newMessage = { id: crypto.randomUUID(), role, content };
state.messages.push(newMessage); // 직접 수정 (불변성 위반)
return { messages: state.messages };
};
// 올바른 방법 (불변성 유지)
const addMessage = (role, content) =>
set((state) => {
const updatedMessages = [...state.messages, {
id: crypto.randomUUID(),
role,
content,
timestamp: new Date()
}];
return { messages: updatedMessages }; // 새 배열 반환
});
반응형
'한번쯤 들어본 FE 기술' 카테고리의 다른 글
[typescript] type 과 interface (0) | 2025.03.12 |
---|---|
비동기 데이터 처리(React Query) (0) | 2025.03.12 |
HTML link 태그의 rel 속성, preconnect, preload, prefetch (0) | 2025.02.12 |
웹 성능을 최적화 하는 여러가지 방법 (1) | 2024.09.23 |
vue.js 번들 크기 줄이기 (0) | 2024.09.23 |