React 컴파일러(React Compiler) 핵심 정리 – 자동 메모이제이션 시대의 React 성능 최적화

okorion·2025년 12월 3일

1. React 컴파일러가 하는 일 – “useMemo를 자동으로 써주는 컴파일러”

1.1 개념 요약

  • 빌드 타임 전용 도구

    • 런타임 코드가 아니라, 빌드 단계에서 JS/TS 코드를 분석·변환
  • 자동 메모이제이션

    • 기존 수동 API:

      • useMemo, useCallback, React.memo
    • 컴파일러는:

      • 컴포넌트와 훅 내부의 값/값 그룹을 자동으로 메모이제이션
      • 의존성이 변하지 않으면 재계산/재렌더를 생략하도록 코드 변환
  • React 규칙 기반 최적화

    • “Hook 규칙, 컴포넌트 순수성” 등을 정적으로 분석

    • 규칙 위반이 감지되면:

      • 그 컴포넌트/훅만 최적화에서 제외
      • 나머지 안전한 코드만 최적화 계속

핵심 포인트:

코드 전체가 100% “규칙을 완벽히 지켜야만” 쓰는 컴파일러가 아니라, 문제 없는 부분만 골라서 최적화하는 방식을 취한다.


2. 언제/왜 쓰는 게 맞는가

2.1 이미 메모이제이션을 잘하고 있으면?

  • 이미:

    • 성능 크리티컬 컴포넌트에
    • useMemo, useCallback, memo 등을 잘 써서
    • 불필요한 리렌더를 잡아놨다면
      → 성능 향상 폭은 크지 않을 수 있다.

하지만 현실에서는:

  • 의존성 배열을 정확히 관리하는 것이 어렵고
  • 코드가 쌓일수록 “어디를 메모이제이션해야 하는지” 판단 비용이 커진다.

컴파일러의 가치는 “사람이 놓치기 쉬운 부분을 자동으로 보정”해준다는 데 있다.


2.2 지금 바로 도입해야 하나?

  • 현재 상태:

    • RC(Release Candidate) 단계
    • Meta 내부 및 대형 서비스에서 이미 광범위하게 사용 중
  • 권장 사용법:

    • 전체 앱에 한 번에 켜는 것이 아니라
    • 일부 디렉토리부터 부분 도입 → 점진 확대

정리하면:

  • “안정 버전 나오면 쓰겠다”도 충분히 합리적인 선택

  • 다만 지금부터:

    • ESLint 플러그인으로 규칙 위반을 정리
    • 작은 범위에 컴파일러 시범 적용
    • 문제 패턴/위험 구간 파악
      이 정도까지는 해두는 게 중장기적으로 유리하다.

3. 설치·환경 구성 – 최소 셋업 가이드

3.1 기본 설치 (React 19 기준)

# npm
npm install -D babel-plugin-react-compiler@rc eslint-plugin-react-hooks@^6.0.0-rc.1

# yarn
yarn add -D babel-plugin-react-compiler@rc eslint-plugin-react-hooks@^6.0.0-rc.1
  • babel-plugin-react-compiler@rc

    • 실제 코드 변환(자동 메모이제이션)을 수행하는 Babel 플러그인
  • eslint-plugin-react-hooks@^6.0.0-rc.1

    • 에디터에서 “컴파일러가 기대하는 React 규칙 위반”을 잡아주는 린트 규칙
      → 사실상 컴파일러를 쓰든 안 쓰든 무조건 깔아야 하는 수준

3.2 React 17/18에서 사용 시 추가 셋업

React 19로 바로 올릴 수 없다면:

  1. 런타임 패키지 설치
npm install react-compiler-runtime@rc
  1. Babel 플러그인 설정에 target 명시
// babel.config.js
const ReactCompilerConfig = {
  target: '18', // '17' | '18' | '19'
};

module.exports = function () {
  return {
    plugins: [
      ['babel-plugin-react-compiler', ReactCompilerConfig],
    ],
  };
};
  • react-compiler-runtime

    • React 17/18에서 컴파일된 코드가 돌도록 필요한 API를 제공/폴리필

4. 점진적 도입 전략 – 기존 코드베이스 기준

4.1 “일부 디렉토리부터 켜기”

컴파일러는 JS의 동적 특성 때문에 모든 위험 케이스를 완벽히 잡지 못한다.
그래서 기존 프로젝트엔 다음 전략이 권장된다.

const ReactCompilerConfig = {
  sources: (filename) => {
    return filename.indexOf('src/path/to/dir') !== -1;
  },
};
  • sources 필터를 이용해:

    • 특정 디렉토리(예: src/feature/dashboard)만 컴파일
  • 점점 자신이 생기면:

    • 다른 디렉토리 추가
    • 최종적으로 전체 앱 적용까지 확대 가능

4.2 ESLint 위반은 “바로 다 고칠 필요 없다”

  • eslint-plugin-react-hooks / eslint-plugin-react-compiler
    → 에디터에서 React 규칙 위반을 표시

  • 이 말은:

    • 해당 컴포넌트/훅은 컴파일러가 최적화를 건너뛰었다는 뜻
  • 하지만:

    • “위반 0개가 될 때까지 컴파일러를 쓰지 말라”는 의미가 아니다.
    • 속도에 맞춰 하나씩 고치고, 그때마다 최적화 커버리지가 늘어난다고 보면 된다.

5. 라이브러리에서의 사용 – 선 컴파일 후 배포

라이브러리(컴포넌트/훅 패키지)를 만드는 입장이라면:

  • 원칙:

    • 컴파일러는 원본 소스 코드에 대해 동작해야 한다.
    • 앱 빌드 파이프라인에서 라이브러리까지 다시 컴파일하는 건 권장되지 않는다.

따라서 라이브러리 쪽 패턴은:

  1. 라이브러리 유지보수자가:

    • 자체 빌드에서 React 컴파일러 적용
    • 충분히 테스트
  2. 컴파일된 코드를 npm에 배포

  3. 앱 개발자는:

    • 별도 React 컴파일러 없이도
    • 라이브러리의 자동 메모이제이션 혜택을 그대로 사용

React 17/18 타겟이라면:

  • 라이브러리 쪽에서:

    • target 설정
    • react-compiler-runtime직접 의존성에 추가
  • 런타임이 앱의 React 버전에 맞춰 적절히 동작하도록 구성

주의점:

  • 라이브러리 코드는:

    • 탈출구, 고급 패턴, 동적 로직이 많아서
    • 컴파일러 버그를 유발하기 쉬운 환경
  • 반드시:

    • 충분한 테스트
    • 이상 케이스는 "use no memo"로 국소적으로 제외

6. 빌드 도구별 적용법 요약

6.1 Babel (공통 기본)

npm install babel-plugin-react-compiler@rc
// babel.config.js
const ReactCompilerConfig = { /* ... */ };

module.exports = function () {
  return {
    plugins: [
      ['babel-plugin-react-compiler', ReactCompilerConfig], // 반드시 최우선!
      // 나머지 플러그인들...
    ],
  };
};
  • 중요: babel-plugin-react-compiler가장 먼저 실행되어야 한다.

    • 사운드한 분석을 위해 원본 AST 정보가 필요하기 때문

6.2 Vite

// vite.config.js
const ReactCompilerConfig = { /* ... */ };

export default defineConfig(() => {
  return {
    plugins: [
      react({
        babel: {
          plugins: [
            ['babel-plugin-react-compiler', ReactCompilerConfig],
          ],
        },
      }),
    ],
  };
});

6.3 Remix (Vite 기반)

npm install vite-plugin-babel
// vite.config.js
import babel from 'vite-plugin-babel';

const ReactCompilerConfig = { /* ... */ };

export default defineConfig({
  plugins: [
    remix({ /* ... */ }),
    babel({
      filter: /\.[jt]sx?$/,
      babelConfig: {
        presets: ['@babel/preset-typescript'], // TS 사용 시
        plugins: [
          ['babel-plugin-react-compiler', ReactCompilerConfig],
        ],
      },
    }),
  ],
});

6.4 기타 도구

  • Next.js

    • 공식 문서에서 React 컴파일러 섹션 제공
  • Webpack

    • 커뮤니티에서 제공하는 로더 사용
  • Expo / React Native(Metro)

    • Metro가 Babel을 사용하므로, Babel 설정과 동일한 원리
  • Rspack / Rsbuild

    • 각 도구의 문서에서 React 컴파일러 연동 방법 제공

공통 패턴:

어떤 빌드 도구를 쓰든, 결국 Babel 단계에 babel-plugin-react-compiler를 가장 먼저 꽂아 넣는다가 핵심이다.


7. 디버깅·Troubleshooting – 문제 생겼을 때 보는 체크리스트

7.1 DevTools로 “최적화 여부” 확인

  • React DevTools v5.0+ / React Native DevTools:

    • 컴파일러에 의해 최적화된 컴포넌트 옆에
      “Memo ✨” 배지 표시
  • 이걸로:

    • 어느 컴포넌트가 자동 메모이제이션 되었는지 빠르게 파악 가능

7.2 컴파일 후 앱이 이상하게 동작할 때

가능한 원인:

  1. ESLint가 이미 잡은 “React 규칙 위반”

    • 이 경우, 해당 컴포넌트/훅은 컴파일러가 건너뛴 상태

    • 동작이 이상하다면:

      • 애초에 기존 코드 자체 버그일 가능성도 체크
  2. JS의 동적 특성 때문에 컴파일러가 놓친 케이스

    • 즉, 정의되지 않은 동작:

      • 무한 루프
      • 예상치 못한 상태 변경 등

이런 경우:

  • 먼저 문제가 되는 컴포넌트/훅에 다음을 넣어 강제로 제외:
function SuspiciousComponent() {
  "use no memo"; // React 컴파일러 최적화에서 제외
  // ...
}
  • 문제가 사라지면:

    • 컴파일러가 해당 코드를 잘못 최적화했다는 뜻

    • 이후:

      • 문제를 최소 예제로 축소
      • 공식 플레이그라운드에 재현
      • 버그 리포트로 공유

7.3 "use no memo" 사용 시 주의점

  • 목적:

    • 특정 컴포넌트/훅을 임시로 컴파일러에서 제외하는 탈출구
  • 문제:

    • 한 번 붙이면 해당 부분은 영구적으로 제외된다.
    • 코드를 나중에 수정해도, 이 지시어가 있는 한 계속 건너뛴다.
  • 권장 사용 패턴:

    1. 긴급하게 문제를 회피할 때만 사용

    2. 문제 원인 파악 후:

      • 지시어 제거 → 문제가 재발하는지 확인
      • 재발한다면 최소 재현을 만들어 이슈로 보고

7.4 컴파일러가 가정하는 것들

  • 코드가 올바른 JS라는 전제

  • 널/옵셔널 안전성:

    • 널 가능 값에 접근하기 전에 반드시 체크

      • if (obj.nullableProp) { obj.nullableProp.foo }
      • 또는 obj.nullableProp?.foo
    • TS라면 strictNullChecks 활성화 권장

  • React 규칙 준수:

    • 훅은 최상위에서만 호출
    • 컴포넌트는 side-effect를 적절히 제한
    • 기타 규칙은 ESLint 플러그인으로 검증

8. 마무리 정리

React 컴파일러의 핵심 포인트:

  • 무엇을 해주는가

    • React 코드에서 “의미 있는 변경이 없는 부분”을 자동으로 메모이제이션
    • 수동 useMemo/useCallback 의존도를 줄이고, 놓치는 구석을 보완
  • 어떻게 도입하는가

    • babel-plugin-react-compiler@rc + eslint-plugin-react-hooks
    • 일부 디렉토리부터 부분 적용 → 점진적으로 확장
    • React 17/18도 react-compiler-runtime + target 설정으로 지원
  • 어떻게 검증하는가

    • DevTools의 “Memo ✨” 배지
    • 문제 시 "use no memo"로 국소 제외 후 원인 추적
    • ESLint 출력과 플레이그라운드를 활용한 최소 재현

본질적으로 이 도구는 “리렌더 최적화의 책임을 사람에서 컴파일러로 옮기는 시도”다.
대신, 전제 조건(React 규칙, null 안전 등)을 지키는 쪽으로 코드베이스를 정리해 두는 것이 필수다.


참고 - React 컴파일러(React Compiler)

profile
okorion's Tech Study Blog.

0개의 댓글