Notice
Recent Posts
Recent Comments
Link
«   2026/01   »
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
Tags
more
Archives
Today
Total
관리 메뉴

likeornament 님의 블로그

React Compiler로 최적화 끝낼 수 있을까? 수동 메모이제이션과 React Compiler의 성능 비교 본문

카테고리 없음

React Compiler로 최적화 끝낼 수 있을까? 수동 메모이제이션과 React Compiler의 성능 비교

likeornament 2025. 11. 1. 18:48

📌 문제 상황 

PR 학습 페이지

 

PR 학습 페이지는 사용자가 직접 텍스트를 입력하고, 단계별로 이동하며, AI 피드백을 확인하는 과정을 반복하는 상호작용 중심의 화면이다.
눈에 띄는 버벅임은 없었지만, DevTools로 렌더링을 확인해보니

textarea에 값이 입력될 때마다 페이지 전체가 리렌더링되고 있었다.

 

특히 우측의 “변경된 파일”(ChangedFiles) 컴포넌트에 그려지는 파일은 2개뿐이지만,
코드 줄 수만큼 <tr><td>가 생성되기 때문에 코드가 길어질수록 DOM 노드가 기하급수적으로 늘어나는 구조였다.
그런데 입력이나 버튼 클릭 같은 사소한 상태 변화에도 이 컴포넌트가 매번 리렌더되고 있어, 성능 부담이 누적될 가능성이 있었다.

 

현재 데이터 크기에서는 체감 성능 저하가 없지만,
학습 데이터가 누적되고 코드 길이가 늘어날 경우 렌더링 비용은 무시할 수 없게 된다.
따라서 실제 사용자 경험에 문제가 생기기 전에, 구조적으로 불필요한 리렌더링을 줄이고자
React.memo + useCallback 적용 전후, 그리고 React Compiler 적용 결과까지 비교해보았다.

 


📌 PR 학습 페이지 컴포넌트 구조

 

PR 페이지의 전체 화면 구성은 다음과 같다.

  • PrLearningContainer (최상위 컨테이너)
    페이지에서 사용되는 데이터, 상태, 메서드를 모두 관리하는 최상위 컨테이너로, 화면 전체를 감싸는 핵심 컴포넌트이다.
  • Header (상단)
    왼쪽에는 나가기(X) 버튼, 오른쪽에는 “변경된 파일 보기” 버튼이 있으며
    현재 진행 중인 step 번호를 표시한다.
  • PrMainContent (좌측 본문 영역)
    AI 질문과 사용자 입력을 받는 textarea, AI 댓글이 포함된 학습의 핵심 영역이다.
  • ChangedFiles (우측 코드 영역)
    변경 전/후 코드 파일을 보여주는 영역으로, 파일의 코드 줄 수만큼 DOM 요소가 생성되는 구조이다.
  • BottomButton (하단 배너 영역, PrMainContent에 포함)
    다음 단계로 넘어가는 버튼이 표시된다.

 

모든 학습 상태는 PrLearningContainer 한 곳에서 관리된다.

const [replies, setReplies] = useState<string[]>([]); // PrMainContent의 textarea
const [showExitConfirm, setShowExitConfirm] = useState<boolean>(false); // PrLearningContainer
const [showCompletion, setShowCompletion] = useState<boolean>(false); // PrLearningContainer
const [currentStep, setCurrentStep] = useState(1); // Header
const [feedbacks, setFeedbacks] = useState<Feedback[]>([]); // PrMainContent
const [showFiles, setShowFiles] = useState<boolean>(false); // ChangedFiles

 

즉, textarea 입력시 setReplies가 호출되면, prLearningContainer의 상태가 변경되고

→ 부모(PrLearningContainer)가 리렌더

→ Header, PrMainContent, ChangedFiles 등 모든 자식 컴포넌트가 함께 리렌더된다.


📌 리렌더링이 발생하는 구조

1. 사용자가 PrMainContent의 textarea에 값을 입력하면 setReplies를 호출한다.

2. setReplies에 의해 replies 상태가 변경된다.

3. PrLearningContainer의 state가 변경되었으므로 본인을 리렌더링한다.

 

PrLearningContainer가 리렌더링되면

  • Header
  • textarea가 있는 PrMainContent
  • ChangedFiles

props를 통해 연결된 모든 자식 컴포넌트가 함께 리렌더링된다.

 

즉, “textarea만 바뀌었는데 화면 전체가 리렌더되는 이유”는
상태가 최상위 컴포넌트에서 모여 있고, 해당 상태가 여러 자식에게 props로 전달되는 구조이기 때문이다.

 

이제 이 문제를 React.memouseCallback을 사용해 먼저 수동 최적화를 진행해보겠다.


✅ 수동 최적화

1. React.memo 적용

export default memo(function Header(props) { ... });
export default memo(function ChangedFiles({ prChangedFiles }) { ... });

 

textarea 입력 시 replies가 변경되면 PrLearningContainer와 PrMainContent만 영향을 받는다.

하지만 HeaderChangedFiles replies와 무관한 props만 받고 있기 때문에 다시 렌더링 될 필요가 없다.


그래서 위 컴포넌트들을 React.memo로 감싸 불필요한 리렌더링을 막았다.

 

type Props = {
  currentStep: number,
  prComments: PrComments,
}

export default memo(function Comment({ currentStep, prComments }: Props) {
  return (
    <WhiteBox>
      <h3 className="font-medium mb-2 text-sm md:text-base">
      	{currentStep === 1 ? "PR 설명 작성" : "리뷰어 답변"}
      </h3>
      <p className="text-xs md:text-sm text-gray-600">
        {currentStep === 1 ? prComments.comments[0].content : "리뷰어의 코멘트에 답변해주세요."}
      </p>
    </WhiteBox>
  )
})

 

또한 PrMainContent 내부에서도 AI 질문 영역은 textarea 입력과 전혀 관련이 없지만,

부모 리렌더가 발생할 때 함께 렌더되고 있었기 때문에, Comment 컴포넌트로 분리 후 memo를 적용했다.

 

2. useCallback 적용

React.memo를 적용해도, props로 전달되는 함수가 매 렌더마다 새로 생성되면

자식 입장에서는 "props 변경"으로 판단해 리렌더된다.

 

const [showFiles, setShowFiles] = useState<boolean>(false);
const [showExitConfirm, setShowExitConfirm] = useState<boolean>(false);
const [currentStep, setCurrentStep] = useState<number>(1);

 

 

이 상태를 변경하는 함수들은 Header에 props로 전달되는데,

매 렌더마다 새로운 함수가 생성되면 메모이제이션 효과가 사라진다.

 

const handleShowFiles = useCallback(() => {
  setShowFiles(true);
}, []);

const handleExit = useCallback(() => {
  setShowExitConfirm(true);
}, []);

const handleStepChange = useCallback((step: number) => {
  setCurrentStep(step);
}, []);

 

그래서 이 함수들을 useCallback으로 감싸 동일 참조를 유지하도록 했다.

이제 Header는 매번 새 함수를 받지 않기 때문에  memo가 제대로 동작하고, 리렌더링을 방지할 수 있다.

 

적용 결과

React.memo와 useCallback 적용 화면

 

최적화 전에는 textarea 입력 시 

prLearningContainer, Header, ChangedFiles 등

거의 모든 컴포넌트가 함께 리렌더링됐다.

 

최적화 후에는 

PrLearningContainerPrMainContent만 리렌더되고,

이와 무관한 컴포넌트들은 리렌더되지 않는 것을 확인할 수 있다.

 

다음으로는 React Compiler를 적용한 자동 최적화를 진행 후 결과를 비교해보도록 하겠다.


✅ 자동 최적화 (React Compiler)

설치

npm install -D babel-plugin-react-compiler

 

 

설정 추가

import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  reactCompiler: true,
}
 
export default nextConfig

 

이 설정 이후부터는 별도의 memo, useCallback 없이도

React Compiler가 자동으로 컴포넌트를 분석해 메모이제이션을 적용한다.

 

 

react devtools 화면

 

React Compiler가 자동 최적화한 컴포넌트는

React DevTools에서 컴포넌트 이름 옆에 Memo 라벨이 붙는다.

 

이를 통해 PrLearningContainer 뿐만 아니라 자식 컴포넌트들도 자동 메모이제이션 대상이 됐다는 것을 확인할 수 있었다.

 

React Compilier 적용 화면

 

그런데 textarea에 값을 입력하면 Header와 BottomButton, Comment 컴포넌트들은 리렌더링이 되지 않지만
우측의 ChangedFiles 영역이 DevTools에서 반짝이며 리렌더링 되는 것처럼 보인다.

 

useEffect(() => {
  console.log("ChangedFiles 실제 리렌더 감지!");
});

 

하지만 useEffect 내부에서 console.log로 확인해보니 콘솔에 메시지가 찍히지 않았다.

DevTools에서 보이는 깜빡임은 "브라우저 페인트" 영향일 뿐 React 내부 렌더링은 발생하지 않았던 것이다.

 

즉, React Compiler가 ChangedFiles도 props 변화가 없다는 것을 감지해 

자동으로 메모이제이션을 적용한 것이다.


✅ 결론

항목 React.memo + useCallback React Compiler
적용 방식 직접 하나씩 감싸고 최적화 설정 한 줄로 자동 메모이제이션
유지보수 컴포넌트 추가 시 반복 필요 자동 처리
최종 결과 리렌더 줄어듦 동일 수준으로 리렌더 감소

 

결론적으로 React Compiler 적용만으로도 수동 최적화와 거의 동일한 효과를 확인했다.

 

그럼에도 React Compiler가 모든 경우에 개발자의 의도를 100% 정확하게 메모이제이션해주는 것은 아니다.


상태, props, 클로저, 외부 의존성 등에 따라 자동 최적화가 예상과 다르게 동작할 수 있기 때문에
DevTools로 실제 렌더링 여부를 확인하고, 필요한 경우 React.memo / useCallback 같은 수동 최적화를 병행하는 것이 가장 안전한 방식이다.