TypeScript는 JSX를 어떻게 변환할까요?

TypeScript의 jsx 옵션은 JSX를 그대로 둘지, React.createElement나 자동 JSX 런타임 호출로 바꿀지를 정합니다. tsconfig.json의 compilerOptions.jsx에 설정하며, 아래 표에서 다섯 가지 옵션의 추가 시점과 출력 방식을 비교합니다.
| 옵션 | 추가 시점 | 설명 |
|---|---|---|
preserve | 초기부터 존재 | JSX를 변환하지 않고 그대로 유지. |
react | 초기부터 존재 | JSX를 React.createElement로 변환. |
react-jsx | TypeScript 4.1 | React 17+의 새로운 JSX Transform을 사용하여 jsx 함수 호출로 변환. |
react-jsxdev | TypeScript 4.1 | 개발 환경에서 디버깅 정보를 추가한 JSX Transform을 사용. |
react-native | TypeScript 2.2 | React Native 프로젝트에서 사용. |
preserve
- JSX를 변환하지 않고 그대로 유지합니다.
- 출력 파일에 JSX가 그대로 남아있습니다.
- 보통 Babel 등 다른 도구에 JSX 처리를 맡길 때 사용합니다.
입력:
const element = <div>안녕하세요?</div>;
출력:
const element = <div>안녕하세요?</div>;
Next.js는 JSX 변환을 자체 빌드 과정에서 처리하므로
jsx: "preserve"를 사용합니다. 다른 값으로 바꾸면 Next.js가 다시preserve로 되돌릴 수 있습니다.
react
- JSX를 React의 React.createElement 호출로 변환합니다.
- React 16 이하 또는 React.createElement가 필요한 환경에서 사용됩니다.
- 이 방식을 사용할 경우 React를 반드시 import해야 합니다.
입력:
import React from "react";
const element = <div>안녕하세요?</div>;
출력:
import React from "react";
const element = React.createElement("div", null, "안녕하세요?");
react-jsx
- TypeScript 4.1 이상에서 지원하는 설정으로, React 17+ 및 백포트된 16.14.0/15.7.0/0.14.10 이상에서 사용할 수 있습니다.
- React의 새로운 JSX 변환 방식(React 17의 JSX Transform)을 사용합니다.
- 일반적으로
jsx·jsxs런타임 호출로 변환하므로 JSX만을 위해 React를 import할 필요가 없습니다. 일부 key와 spread 조합에서는createElement로 변환될 수 있습니다.
입력:
const element = <div>안녕하세요?</div>;
출력:
import { jsx as _jsx } from "react/jsx-runtime";
const element = _jsx("div", { children: "안녕하세요?" });
react-jsxdev
react-jsx와 같은 자동 런타임을 사용하되 개발용 디버깅 정보를 추가합니다.
- 디버깅 목적으로 개발 환경(Development Mode)에서 사용합니다.
- 생성된 JSX 코드에 디버깅에 유용한 정보(예: 파일 이름, 라인 번호 등)를 포함합니다.
입력:
const element = <div>Hello, world!</div>;
출력:
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
const element = _jsxDEV(
"div",
{ children: "Hello, world!" },
undefined,
false,
{
fileName: "src/App.js",
lineNumber: 2,
columnNumber: 17,
},
this
);
react-native
React Native 프로젝트에서 JSX를 처리하기 위한 옵션입니다.
preserve와 마찬가지로 JSX를 유지하지만 출력 확장자는.jsx대신.js입니다.- TypeScript는 JSX를 변환하지 않고 그대로 유지하며, React Native의 Babel 설정에서 변환을 처리합니다.
자동 JSX 런타임을 도입한 이유
요약
- JSX만을 위해 React를 가져올 필요가 없습니다.
- 번들에서 불필요한 import를 줄일 수 있습니다.
React.createElement에 묶이지 않은 전용 런타임을 통해 이후 변환 방식을 확장할 수 있습니다.
React import가 필요 없는 이유
JSX Transform 도입 이전에는 JSX를 사용하기 위해 React를 반드시 import해야 했습니다. React 17의 자동 런타임을 사용하면 JSX만을 위해 React를 import할 필요가 없습니다.
1-1. 이전 방식 (React 16 이하)
JSX는 React.createElement로 변환되었기 때문에 React를 반드시 import해야 했습니다:
import React from "react";
const App = () => <div>Hello, world!</div>;
1-2. JSX Transform 도입 이후 (React 17 이상)
React import 없이도 JSX가 동작합니다.
const App = () => <div>Hello, world!</div>;
번들 크기 개선과 향후 최적화 기반
JSX Transform은 기존 React.createElement 방식의 제약을 걷어내도록 설계되었습니다.
2-1. 기존 방식 (React.createElement)
기존 변환기는 JSX를 다음과 같은 React.createElement 호출로 바꿨습니다.
입력:
import React from "react";
const element = <div>Hello, world!</div>;
출력:
import React from "react";
const element = React.createElement("div", null, "Hello, world!");
이 방식은 JSX를 사용하는 모든 파일에서 React를 import해야 했고, React.createElement가 허용하는 동작 범위가 넓어 React가 내부 최적화를 시도하기 어려웠습니다.
2-2. 새로운 방식 (JSX Transform)
React 17의 JSX Transform은 전용 런타임 함수인 jsx를 호출하는 코드를 생성합니다.
입력:
const element = <div>Hello, world!</div>;
출력:
import { jsx as _jsx } from "react/jsx-runtime";
const element = _jsx("div", { children: "Hello, world!" });
다만 이 변화로 성능이 극적으로 좋아지지는 않습니다. React import가 필요 없어져 개발 경험이 나아지고 번들 크기가 조금 줄었으며, 향후 React가 내부 최적화를 도입할 기반이 마련되었습니다.
2-3. React 17 이후의 새로운 기능 지원
전용 JSX 런타임이 생기면서 이후 React 기능과 변환 방식을 확장할 여지가 생겼습니다.
- React 팀은 기존
React.createElementAPI에만 의존하지 않고 변환 결과를 확장할 수 있습니다. - 이 런타임은 JSX 컴파일 방식에 관한 것이며, 서버에서 실행되는 컴포넌트 모델인 React Server Components와는 다른 개념입니다.
2-4. 변환 도구와 개발 환경 설정
Babel·SWC·TypeScript 같은 변환 도구에서 자동 런타임을 설정해야 합니다. ESLint도 React import가 필요 없다는 전제에 맞게 구성합니다. Webpack이나 Vite 자체가 이 변환만으로 빌드 속도 향상을 보장하지는 않습니다.
정리
TypeScript가 JSX 변환까지 맡는 프로젝트라면 react-jsx로 자동 런타임을 사용할 수 있습니다. JSX만을 위한 React import가 필요 없어집니다.
React 프로젝트라도 Next.js처럼 다른 도구가 JSX 변환을 맡는다면 preserve 등 해당 도구가 요구하는 설정을 따릅니다.
| 옵션 | 설명 | 장점 | 제한사항 |
|---|---|---|---|
preserve | JSX를 변환하지 않고 그대로 유지. | 외부 트랜스파일러와의 호환성 | TypeScript에서 JSX 처리 안함. |
react | JSX를 React.createElement로 변환. | React 16 이하와 호환. | React Import가 필수. |
react-jsx | React 17+의 새로운 JSX Transform을 사용하여 jsx 함수 호출로 변환. | React Import 불필요, 최신 방식. | React 17+ 및 백포트된 16.14.0, 15.7.0, 0.14.10 이상에서 사용 가능. |
react-jsxdev | react-jsx와 비슷하지만, 디버깅 정보를 추가한 JSX Transform을 사용. | 디버깅에 유용. | 프로덕션 환경에서는 사용하지 않음. |
react-native | React Native 환경에서 JSX를 변환하지 않고 그대로 유지. | React Native 환경에서 사용 가능. | 출력된 JSX를 다른 도구에서 변환해야 함. |
