한번쯤 들어본 FE 기술

성능 최적화

프론트루나 2025. 3. 12. 07:53
반응형

 

메모이제이션

불필요한 리렌더링을 방지하는 기법

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 }; // 새 배열 반환
  });
반응형