2025년 03월 25일
보통 리액트를 통해 JSX를 접하기 때문에 JSX가 리액트의 전유물이라고 오해하는 경우가 있다.
JSX는 리액트가 등장하면서 페이스북에서 소개한 새로운 구문이긴 하지만, 리액트에 종속적이지 않은 독자적인 문법이다.
ECMAScript라고 불리는 자바스크립트 표준의 일부가 아니기 때문에 JSX가 포함된 코드를 아무 처리 없이 그대로 실행하면 에러가 발생한다. JSX는 트랜스파일러를 거쳐야 비로소 자바스크립트 런타임이 이해할 수 있는 의미 있는 자바스크립트 코드로 변환된다.
자바스크립트에서 JSX가 변환되는 방식을 알려면 리액트에서 JSX를 변환하는 @babel/plugin-transform-react-jsx 플러그인을 알아야 한다. 이 플러그인은 JSX구문을 자바스크립트가 이해할 수 있는 형태로 변환한다. Babel은 JSX 구문을 React.createElement 형태의 함수 호출 코드로 변환하여 ReactElement 객체를 생성할 수 있도록 한다.
const ComponentA = <A required={true}>Hello World</A>;
const ComponentB = <>Hello World</>;
const ComponentC = (
<div>
<span>hello world</span>
</div>
);
위와 같은 JSX코드가 있다고 가정해볼때, 이를 변환한 결과는 아래와 같다.
'use strict';
var componentA = React.createElement(
A,
{
required: true,
},
'Hello World'
);
var ComponentB = React.createElement(React.Fragment, null, 'Hello world');
var ComponentC = React.createElement(
'div',
null,
React.createElement('span', null, 'hello world')
);
리액트 17, 바벨 7.9.0 이후 버전에서 추가된 자동 런타임(autumatic runtime)으로 트랜스파일한 결과는 아래와 같다.
기존에는 모든 JSX가 React.createElement로 변환되기 때문에 반드시 React를 import해야 했다.
자동런타임(automatic runtime)에서는 Babel이 JSX를 jsx() 또는 jsxs() 함수 호출로 변환하며, 이 함수들은 react/jsx-runtime 모듈에서 자동으로 가져온다.
이 덕분에 JSX를 사용하더라도 import React from 'react'를 명시적으로 작성할 필요가 없다.
'use strict';
var _jsxRuntime = require('react/jsx-runtime');
var componentA = (0, _jsxRuntime.jsx)(A, {
required: true,
children: 'Hello World',
});
var ComponentB = (0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
children: 'Hello world',
});
var ComponentC = (0, _jsxRuntime.jsx)('div', {
children: (0, _jsxRuntime.jsx)('span', {
children: 'hello world',
}),
});
두 결과물에 약간의 차이가 있지만 다음과 같은 공통점이 있다.
이 점을 활용하면 경우에 따라 다른 JSXElement를 렌더링해야 할 때 굳이 요소 전체를 감싸지 않더라도 처리할 수 있다. 이는 JSXElement만 다르고, JSXAttrirbutes, JSXChildren이 완전히 동일한 상황에서 중복 코드를 최소화할 수 있어 유용하다.
아래 코드 예시는 props의 여부에 따라 children 요소만 달라지는 경우로, 이 경우 불필요한 코드 중복이 일어난다.
import { PropsWithChildren, createElement } from 'react';
function TextOrHeading({
isHeading,
children,
}: PropsWithChildren<{ isHeading: boolean }>) {
return isHeading ? (
<h1 className="text">{children}</h1>
) : (
<span className="text">{children}</span>
);
}
JSX의 반환값이 결국 React.createElement로 귀결된다는 사실을 파악하면 아래와 같은 형식으로 리팩터링이 가능하다.
import { createElement } from 'react';
function TextOrHeading({
isHeading,
children,
}: PropsWithChildren<{ isHeading: boolean }>) {
return createElement(
isHeading ? 'h1' : 'span',
{ className: 'text' },
children
);
}
참고자료
모던 리액트 Deep Dive 2장 리액트 핵심 요소 깊게 살펴보기 참조