TypeScript가 JSX를 변환하는 방법과 JSX Transform에 대해
TypeScript 4.1 이상부터는 JSX 코드를 트랜스파일링하기 위한 다양한 옵션을 제공합니다. 이러한 옵션은 React의 다양한 환경(예: React 16, React 17+, React Native)에 맞춰 JSX를 효율적으로 처리하는 데 사용됩니다. 이번 글에서는 TypeScript가 JSX를 처리하는 방식과 React 17에서 도입된 새로운 JSX Transform의 이점을 설명합니다.
- preserve
- react
- react-jsx
- react-jsxdev
- react-native
이 옵션들은 tsconfig.json 파일의 compilerOptions에 설정하는 jsx 속성에서 사용할 수 있습니다. 설정에 따라 TypeScript가 JSX 코드를 변환하거나 유지하는 방식이 달라집니다.
아래는 옵션별 TypeScript 스펙 추가 시점과 간략한 설명입니다.
옵션 | 추가 시점 | 설명 |
---|---|---|
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 4.0 | React Native 프로젝트에서 사용. |
preserve
- JSX를 변환하지 않고 그대로 유지합니다.
- 출 력 파일에 JSX가 그대로 남아있습니다.
- 보통 Babel 등 다른 도구에서 JSX를 처리하도록 위임하는 경우 사용합니다.
// 입력
const element = <div>안녕하세요?</div>;
// 출력
const element = <div>안녕하세요?</div>;
Next.js에서 tsconfig.json의 ‘jsx’ 옵션을 ‘preserve’로 설정하지 않으면 자동으로 ‘preserve’로 바꿔줍니다. 그 이유는 Next.js에서 자체 구현한 JSX transform을 제공하기 때문입니다.
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 이상에서 지원하는 설정으로, React17 이상에서 사용됩니다.
- React의 새로운 JSX 변환 방식(React 17의 JSX Transform)을 사용합니다.
- React.createElement를 사용하지 않으며, React를 import하지 않아도 됩니다.
// 입력
const element = <div>안녕하세요?</div>;
// 출력
import { jsx as _jsx } from "react/jsx-runtime";
const element = _jsx("div", { children: "안녕하세요?" });
react-jsxdev
React 17+의 새로운 JSX Transform을 사용하면서 디버깅 정보를 추가하기 위해 사용합니다.
- 디버깅 목적으로 개발 환경(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를 처리하기 위한 옵션입니다.
- React Native 환경에서는 브라우저 DOM 대신 Native Components를 사용하므로 JSX 변환 방식이 다릅니다.
- TypeScript는 JSX를 변환하지 않고 그대로 유지하며, React Native의 Babel 설정에서 변환을 처리합니다.
JSX를 변환하는 방법이 React.createElement에서 JSX Transform으로 변경된 이유
요약
- React Import 필요성을 제거하여 개발자 경험(DX)을 개선.
- 런타임 성능 최적화를 통해 더 빠른 실행과 적은 오버헤드를 제공.
- 향후 React 기능 확장을 지원하는 유연성과 호환성 확보.
- 코드가 더 간결하고 유지보수하기 쉬워짐.
1. React Import 불필요
JSX Transform 도입 이전에는 JSX를 사용하기 위해 React를 반드시 import해야 했습니다. 그러나 React 17부터는 React Import 없이 JSX를 사용할 수 있도록 변경되었습니다.
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>;
2. 성능 최적화
JSX Transform은 기존 React.createElement 방식보다 더 효율적으로 동작하도록 설계되었습니다.
2-1. 기존 방식 (React.createElement)
JSX를 사용할 때마다 React는 다음과 같이 동작했습니다:
import React from "react";
const element = <div>Hello, world!</div>;
// 컴파일 후
const element = React.createElement("div", null, "Hello, world!");
이 방식은 런타임에서 React.createElement를 호출하여 객체를 생성하므로 추가적인 오버헤드가 있었습니다.
여기서 말하는 오버헤드란 아래와 같습니다.
- 런타임에서 createElement가 호출될 때마다 연산이 반복
- 추가적인 React 객체 참조로 불필요한 메모리 소비
- React 객체 내 다른 API가 번들에 포함될 가능성이 있음 (no tree shaking)
2-2. 새로운 방식 (JSX Transform)
React 17의 JSX Transform은 런타임 오버헤드를 줄이기 위해 더 효율적인 코드를 생성합니다:
const element = <div>Hello, world!</div>;
// 컴파일 후
import { jsx as _jsx } from "react/jsx-runtime"; // 컴파일러가 자동으로 런타임에서 넣어주는 import문
const element = _jsx("div", { children: "Hello, world!" });
이로써 React 자체의 오버헤드를 줄이고, JSX 변환 과정을 더 최적화하며, 코드 크기를 줄이고 실행 속도를 개선합니다.
2-3. React 17 이후의 새로운 기능 지원
React 17은 JSX Transform을 통해 향후 추가될 React 기능 및 변경사항에 대한 더 나은 지원을 제공합니다.
- React 팀이 기존 React.createElement API에 의존하지 않고 새로운 기능을 쉽게 추가할 수 있는 유연성을 확보.
- React의 향후 업데이트와 새로운 컴파일러(예: React Server Components)와의 호환성을 더 쉽게 보장.
2-4. 타사 도구 및 빌드 환경과의 호환성
JSX Transform은 React 외의 다른 빌드 도구와도 더 나은 통합을 제공합니다.
- Babel: JSX Transform은 Babel 플러그인을 통해 더 쉽게 통합.
- ESLint: React Import가 필요 없으므로 불필요한 import에 대한 경고를 줄임.
- Webpack/Vite: 최신 빌드 도구와 더 빠르고 간결하게 동작.
Recap
React 17 이상을 사용 중이라면 react-jsx를 사용하는 것이 권장됩니다. 이는 React의 새로운 JSX 변환 방식을 활용하며, React를 명시적으로 import하지 않아도 되기 때문입니다.
React 외의 환경(Babel 기반 프로젝트 등)에서는 preserve를 사용하여 다른 도구에서 JSX 를 처리하도록 위임할 수 있습니다.
옵션 | 설명 | 장점 | 제한사항 |
---|---|---|---|
preserve | JSX를 변환하지 않고 그대로 유지. | 외부 트랜스파일러와의 호환성 | TypeScript에서 JSX 처리 안함. |
react | JSX를 React.createElement 로 변환. | React 16 이하와 호환. | React Import가 필수. |
react-jsx | React 17+의 새로운 JSX Transform을 사용하여 jsx 함수 호출로 변환. | React Import 불필요, 최신 방식. | React 17 이상에서만 사용 가능. |
react-jsxdev | react-jsx 와 비슷하지만, 디버깅 정보를 추가한 JSX Transform을 사용. | 디버깅에 유용. | 프로덕션 환경에서는 사용하지 않음. |
react-native | React Native 환경에서 JSX를 변환하지 않고 그대로 유지. | React Native 환경에서 사용 가능. | 브라우저 DOM에서는 동작하지 않음. |