본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 10장: React 18에서 19로 마이그레이션
2026년 2월 10일·웹 개발·

10장: React 18에서 19로 마이그레이션

React 18에서 19로 안전하게 업그레이드하는 단계별 가이드입니다. 제거된 API, 타입 변경, 동작 변화, 자동 마이그레이션 도구를 다룹니다.

14분691자8개 섹션
reactnextjsperformancefrontendtypescript
공유
react19-rsc10 / 11
1234567891011
이전9장: 성능 최적화 전략과 베스트 프랙티스다음11장: 실전 프로젝트 - React 19 풀스택 앱 구축

9장에서 React 19의 성능 최적화 전략을 다루었습니다. 이번 장에서는 기존 React 18 프로젝트를 React 19로 업그레이드하는 구체적인 과정을 안내합니다. 제거된 API, 동작 변화, TypeScript 타입 변경, 자동 마이그레이션 도구 활용법을 단계별로 살펴봅니다.

마이그레이션 전략 개요

React 19로의 마이그레이션은 세 단계로 진행합니다.

1단계: React 18.3으로 먼저 업그레이드

React 18.3은 React 19에서 제거될 API에 대한 deprecation 경고를 추가한 버전입니다. 먼저 18.3으로 업그레이드하여 문제를 미리 파악합니다.

bash
pnpm add react@18.3 react-dom@18.3

콘솔에 나타나는 경고를 모두 수정합니다.

2단계: 자동 마이그레이션 도구 실행

React 팀에서 제공하는 codemod를 실행합니다.

bash
# React 코드 마이그레이션
npx codemod@latest react/19/migration-recipe
 
# TypeScript 타입 마이그레이션
npx types-react-codemod@latest preset-19 ./src

migration-recipe에 포함된 codemod 목록:

Codemod변환 내용
replace-reactdom-renderReactDOM.render → createRoot
replace-string-ref문자열 ref → useRef
replace-act-importreact-dom/test-utils → react
replace-use-form-stateuseFormState → useActionState
prop-types-typescriptPropTypes → TypeScript 타입

3단계: React 19 업그레이드

bash
pnpm add react@^19.0.0 react-dom@^19.0.0
pnpm add -D @types/react@^19.0.0 @types/react-dom@^19.0.0

제거된 API와 대체 방법

ReactDOM.render / ReactDOM.hydrate

렌더링 API 마이그레이션
typescript
// React 18 (제거됨)
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, document.getElementById('root'));
ReactDOM.hydrate(<App />, document.getElementById('root'));
 
// React 19
import { createRoot, hydrateRoot } from 'react-dom/client';
 
// 클라이언트 렌더링
const root = createRoot(document.getElementById('root')!);
root.render(<App />);
 
// 하이드레이션 (SSR)
hydrateRoot(document.getElementById('root')!, <App />);

unmountComponentAtNode

언마운트 마이그레이션
typescript
// React 18 (제거됨)
ReactDOM.unmountComponentAtNode(container);
 
// React 19
const root = createRoot(container);
root.render(<App />);
// 나중에 언마운트
root.unmount();

findDOMNode

findDOMNode 대체
typescript
// React 18 (제거됨)
class MyComponent extends React.Component {
  componentDidMount() {
    const node = ReactDOM.findDOMNode(this);
  }
}
 
// React 19: useRef 사용
function MyComponent() {
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const node = ref.current;
  }, []);
  return <div ref={ref}>...</div>;
}

react-dom/test-utils

테스트 유틸 마이그레이션
typescript
// React 18 (제거됨)
import { act } from 'react-dom/test-utils';
 
// React 19
import { act } from 'react';

PropTypes

PropTypes 대체
typescript
// React 18 (제거됨)
import PropTypes from 'prop-types';
 
MyComponent.propTypes = {
  name: PropTypes.string.isRequired,
  count: PropTypes.number,
};
 
// React 19: TypeScript 타입
interface MyComponentProps {
  name: string;
  count?: number;
}
 
function MyComponent({ name, count = 0 }: MyComponentProps) {
  return <div>{name}: {count}</div>;
}

defaultProps (함수 컴포넌트)

defaultProps 대체
typescript
// React 18 (함수 컴포넌트에서 제거됨)
function Button({ size, variant }) { /* ... */ }
Button.defaultProps = {
  size: 'medium',
  variant: 'primary',
};
 
// React 19: ES6 기본값
function Button({
  size = 'medium',
  variant = 'primary',
}: ButtonProps) {
  // ...
}
Info

클래스 컴포넌트의 defaultProps는 React 19에서도 유지됩니다. 함수 컴포넌트에서만 제거되었습니다.

문자열 ref

문자열 ref 대체
typescript
// React 18 (제거됨)
class MyComponent extends React.Component {
  render() {
    return <input ref="myInput" />;
  }
  handleClick() {
    this.refs.myInput.focus();
  }
}
 
// React 19: useRef 또는 콜백 ref
function MyComponent() {
  const inputRef = useRef<HTMLInputElement>(null);
  function handleClick() {
    inputRef.current?.focus();
  }
  return <input ref={inputRef} />;
}

Legacy Context

Legacy Context 대체
typescript
// React 18 (제거됨)
class Provider extends React.Component {
  getChildContext() {
    return { theme: 'dark' };
  }
  static childContextTypes = {
    theme: PropTypes.string,
  };
}
 
class Consumer extends React.Component {
  static contextTypes = {
    theme: PropTypes.string,
  };
  render() {
    return <div>{this.context.theme}</div>;
  }
}
 
// React 19: createContext + useContext
const ThemeContext = createContext('light');
 
function Provider({ children }: { children: React.ReactNode }) {
  return (
    <ThemeContext value="dark">
      {children}
    </ThemeContext>
  );
}
 
function Consumer() {
  const theme = use(ThemeContext);
  return <div>{theme}</div>;
}

TypeScript 변경사항

useRef 필수 인자

useRef 변경
typescript
// React 18: 인자 없이 호출 가능
const ref = useRef<HTMLDivElement>();  // ref.current: HTMLDivElement | undefined
 
// React 19: 인자 필수
const ref = useRef<HTMLDivElement>(null);  // ref.current: HTMLDivElement | null
 
// undefined를 명시적으로 전달하면 MutableRefObject
const ref = useRef<number>(undefined);  // ref.current: number | undefined

모든 ref가 Mutable

React 19에서 모든 ref는 mutable입니다. ref.current에 값을 할당할 수 있습니다.

Mutable ref
typescript
// React 18: useRef<HTMLDivElement>(null)의 ref.current는 readonly
const ref = useRef<HTMLDivElement>(null);
ref.current = someElement;  // TypeScript 에러
 
// React 19: 모든 ref.current가 writable
const ref = useRef<HTMLDivElement>(null);
ref.current = someElement;  // 허용

ReactElement props 타입

ReactElement 타입 변경
typescript
// React 18
type Props = React.ReactElement['props'];  // any
 
// React 19
type Props = React.ReactElement['props'];  // unknown
 
// 명시적 타입 지정이 필요
type Props = React.ReactElement<{ title: string }>['props'];

ref 콜백 반환 타입

ref 콜백 변경
typescript
// React 18: 암묵적 반환 허용
<div ref={(node) => (myRef = node)} />
 
// React 19: 블록 바디 필요 (클린업 함수와의 혼동 방지)
<div ref={(node) => { myRef = node; }} />

JSX 네임스페이스

JSX 네임스페이스 변경
typescript
// React 18: 전역 JSX 네임스페이스
declare global {
  namespace JSX {
    interface IntrinsicElements {
      'my-element': React.HTMLAttributes<HTMLElement>;
    }
  }
}
 
// React 19: React 모듈 범위
declare module 'react' {
  namespace JSX {
    interface IntrinsicElements {
      'my-element': React.HTMLAttributes<HTMLElement>;
    }
  }
}

동작 변경사항

에러 처리

에러 처리 변경
typescript
// React 18: 에러를 다시 throw
// console.error + window.onerror에 동일 에러가 중복 보고됨
 
// React 19: 에러를 다시 throw하지 않음
// Error Boundary에서 잡힌 에러 → console.error
// 잡히지 않은 에러 → window.reportError
 
// 커스텀 에러 핸들링
const root = createRoot(container, {
  onCaughtError(error, errorInfo) {
    // Error Boundary에서 잡힌 에러 로깅
  },
  onUncaughtError(error, errorInfo) {
    // 잡히지 않은 에러 로깅
  },
});

StrictMode 동작

StrictMode 변경
typescript
// React 18: 이중 렌더링 시 매번 새로 계산
// React 19: 이중 렌더링 시 첫 번째 결과를 재사용 (메모이제이션)
 
// 이로 인해 useMemo, useCallback의 두 번째 호출에서
// 첫 번째 호출의 결과가 재사용됩니다.

Suspense fallback 타이밍

Suspense 변경
typescript
// React 18: fallback 표시에 약간의 지연이 있음
// React 19: fallback이 즉시 표시됨
 
// 이 변경으로 인해 기존에 "눈에 보이지 않던" 짧은 로딩이
// 이제 눈에 보일 수 있습니다. skeleton UI를 더 신경 써야 합니다.

javascript: URL 차단

javascript: URL 차단
typescript
// React 19: src, href에 javascript: URL 사용 시 에러
<a href="javascript:void(0)">클릭</a>  // 에러
<script src="javascript:alert(1)" />    // 에러
 
// 대체 방법
<button onClick={handleClick}>클릭</button>

서드파티 라이브러리 호환성

주요 라이브러리 상태

React 19와의 호환성을 확인해야 하는 주요 라이브러리들입니다.

package.json 업데이트 예시
typescript
{
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "next": "^15.0.0",
    "@tanstack/react-query": "^5.0.0",
    "react-hook-form": "^7.50.0",
    "zustand": "^5.0.0",
    "framer-motion": "^11.0.0"
  }
}
Warning

라이브러리 업그레이드 전에 반드시 해당 라이브러리의 React 19 호환성 문서를 확인하세요. 일부 라이브러리는 메이저 버전 업데이트가 필요할 수 있습니다.

UMD 빌드 제거

React 19에서 UMD 빌드가 제거되었습니다. CDN에서 스크립트 태그로 React를 로드하는 경우 ESM으로 전환해야 합니다.

html
<!-- React 18: UMD -->
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
 
<!-- React 19: ESM -->
<script type="module">
  import React from 'https://esm.sh/react@19';
  import ReactDOM from 'https://esm.sh/react-dom@19/client';
</script>

단계별 마이그레이션 체크리스트

Phase 1: 준비

  • React 18.3으로 업그레이드
  • 모든 deprecation 경고 확인 및 기록
  • 테스트 커버리지 확인 (마이그레이션 검증용)
  • 서드파티 라이브러리의 React 19 호환 버전 확인

Phase 2: 코드 변환

  • codemod 실행 (migration-recipe + preset-19)
  • ReactDOM.render → createRoot 전환
  • forwardRef → ref props 전환 (선택)
  • PropTypes → TypeScript 타입 전환
  • defaultProps → ES6 기본값 전환
  • 문자열 ref → useRef/콜백 ref 전환
  • Legacy Context → createContext 전환
  • useFormState → useActionState 전환
  • act import 경로 변경

Phase 3: 업그레이드

  • React 19 + TypeScript 타입 설치
  • 빌드 성공 확인
  • TypeScript 컴파일 에러 수정 (ref 관련)
  • 전체 테스트 실행 및 통과 확인

Phase 4: 검증

  • 주요 페이지 수동 테스트
  • 하이드레이션 에러 없는지 확인
  • 성능 회귀 없는지 확인 (Lighthouse)
  • E2E 테스트 실행

Phase 5: 새 기능 도입 (선택)

  • Server Components 도입
  • Server Actions 도입
  • React Compiler 활성화
  • 새로운 훅(useActionState, useOptimistic) 적용

점진적 도입 전략

모든 것을 한 번에 바꿀 필요는 없습니다. React 19의 새 기능은 점진적으로 도입할 수 있습니다.

React 19 설치 (Breaking Changes만 수정)
    ↓
forwardRef 제거 (새 코드부터)
    ↓
Server Components 도입 (새 페이지부터)
    ↓
Server Actions 도입 (새 폼부터)
    ↓
React Compiler 활성화 (annotation 모드)
    ↓
기존 useMemo/useCallback 정리

각 단계는 독립적이며, 팀의 상황에 맞게 순서와 속도를 조절할 수 있습니다.

핵심 요약

  • React 18.3으로 먼저 업그레이드하여 deprecation 경고를 확인하고, codemod로 자동 변환한 후 React 19로 업그레이드합니다.
  • ReactDOM.render, findDOMNode, PropTypes, defaultProps(함수 컴포넌트), 문자열 ref, Legacy Context 등이 제거되었습니다.
  • TypeScript에서 useRef는 인자가 필수이며, 모든 ref가 mutable이고, ref 콜백의 암묵적 반환이 금지되었습니다.
  • Suspense fallback이 즉시 커밋되고, 에러가 더 이상 재throw되지 않으며, javascript: URL이 차단됩니다.
  • 서드파티 라이브러리의 React 19 호환성을 반드시 확인하고, UMD 빌드는 ESM으로 전환해야 합니다.
  • 마이그레이션은 점진적으로 진행할 수 있으며, 새 기능은 팀의 속도에 맞게 단계적으로 도입합니다.

다음 장에서는 이 시리즈의 마지막으로, React 19의 핵심 기능을 모두 활용한 풀스택 앱을 처음부터 구축합니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#react#nextjs#performance#frontend#typescript

관련 글

웹 개발

11장: 실전 프로젝트 - React 19 풀스택 앱 구축

React 19의 핵심 기능을 모두 활용한 풀스택 북마크 앱을 구축합니다. Server Components, Server Actions, 새로운 훅, Suspense 패턴을 실전에 적용합니다.

2026년 2월 12일·16분
웹 개발

9장: 성능 최적화 전략과 베스트 프랙티스

React 19 애플리케이션의 성능을 극대화하는 전략을 다룹니다. 번들 최적화, 렌더링 성능, Core Web Vitals 개선, 측정 도구 활용법을 배웁���다.

2026년 2월 8일·15분
웹 개발

8장: ref 개선, 메타데이터, 리소스 로딩 API

React 19의 DX 개선사항을 다룹니다. ref를 일반 props로 전달하는 방법, 컴포넌트 내 메타데이터 태그, 리소스 프리로딩 API를 살펴봅니다.

2026년 2월 6일·16분
이전 글9장: 성능 최적화 전략과 베스트 프랙티스
다음 글11장: 실전 프로젝트 - React 19 풀스택 앱 구축

댓글

목차

약 14분 남음
  • 마이그레이션 전략 개요
    • 1단계: React 18.3으로 먼저 업그레이드
    • 2단계: 자동 마이그레이션 도구 실행
    • 3단계: React 19 업그레이드
  • 제거된 API와 대체 방법
    • ReactDOM.render / ReactDOM.hydrate
    • unmountComponentAtNode
    • findDOMNode
    • react-dom/test-utils
    • PropTypes
    • defaultProps (함수 컴포넌트)
    • 문자열 ref
    • Legacy Context
  • TypeScript 변경사항
    • useRef 필수 인자
    • 모든 ref가 Mutable
    • ReactElement props 타입
    • ref 콜백 반환 타입
    • JSX 네임스페이스
  • 동작 변경사항
    • 에러 처리
    • StrictMode 동작
    • Suspense fallback 타이밍
    • javascript: URL 차단
  • 서드파티 라이브러리 호환성
    • 주요 라이브러리 상태
    • UMD 빌드 제거
  • 단계별 마이그레이션 체크리스트
    • Phase 1: 준비
    • Phase 2: 코드 변환
    • Phase 3: 업그레이드
    • Phase 4: 검증
    • Phase 5: 새 기능 도입 (선택)
  • 점진적 도입 전략
  • 핵심 요약