목차
리액트 JSX 구문 확장
바닐라 리액트만으로 마크업을 생성하는 것은 불편하고 번거로운 일이다. 리액트 팀은 마크업을 더 쉽고 편리하게 설명할 수 있도록 JSX를 만들었다. JSX는 리액트에서 마크업을 표현할 때 널리 사용되는 XML과 유사한 자바스크립트 문법 확장이다. 실제 리액트 앱 개발에서는 특별한 경우를 제외하고 대부분 JSX를 사용한다.
// React API + JavaScript
const box = React.createElement(
Box,
{},
shouldShowAnswer(user)
? React.createElement(
Answer,
{ value: false },
'답변: 아니오'
)
: React.createElement(
Box.Comment,
{},
'댓글을 입력해 주세요.'
)
)
// React API + JSX
const box = (
<Box>
{shouldShowAnswer(user) ? (
<Answer value={false}>답변: 아니오</Answer>
) : (
<Box.Comment>댓글을 입력해 주세요.</Box.Comment>
)}
</Box>
)
JSX 구문 확장은 웹 표준이 아니다! JSX는 웹 브라우저에서 바로 해석 가능한 표준 마크업 언어가 아니기 때문에 JavaScript 코드로 변환하는 과정을 거쳐야만 브라우저에서 해석할 수 있다. JSX를 사용해 작성된 마크업을 JavaScript 코드로 변환화는 과정을 트랜스파일(Transpile)이라고 한다. 잘 알려진 JSX 트랜스파일 도구는 Babel과 TypeScript다.
JSX 구문 확장의 특징
규칙 | 설명 | 예시 |
JSX 마크업 | 리액트 엘리먼트로 변환 | <div>...</div> |
문법 준수 | XML 문법을 따름 | <img src={img.src} alt={img.alt} /> |
HTML 속성 | 카멜 표기법 사용 | className, maxLength, readOnly, htmlFor |
WAI-ARIA | 케밥 표기법 사용 | aria-label, aria-hidden |
데이터 속성 | 케밥 표기법 사용 | data-id, data-user-name |
스타일 속성 | 객체({})로 설정 | style={{ color: '#f00', fontSize: 16 }} |
JS 표현식 | 중괄호({}) 안에 사용 | <output>{count + 1}</output> |
선언적 vs. 명령형 프로그래밍
명령형 프로그래밍
전통적이고 일반적인 자바스크립트 프로그래밍 방식이다. 개발자가 운전자(JS)에게 목적지로 이동하는 방법을 일일이 지시해 이동하는 것과 같다.
- 화면을 어떻게(HOW) 그려야 할지 하나하나 지시해야 한다.
- "<section>을 만들고, <h2>를 만들고, <span>을 만들고, ..." 단계별로 직접 조작해야 한다.
- 데이터가 변경되면 직접 DOM을 찾아 값을 바꿔 화면을 업데이트해야 한다.
선언적 프로그래밍
리액트의 프로그래밍 방식은 선언적(Declarative)이다. 개발자가 운전자(React)에게 목적지를 말하면 운전자가 알아서 이동하는 것과 같다.
- 화면에 무엇(WHAT)을 그려야 할지 선언한다.
- "<h1> 요소에 이름을, <span> 요소에 나이를..." 구조를 선언적으로 표현한다.
- 데이터가 변경되면 화면이 자동으로 업데이트된다.
따라서, 리액트는 다음과 같은 장점을 가진다.
- 선언적 방식을 사용하기 때문에 복잡한 UI도 쉽게 만들 수 있다.
- 데이터(상태)가 변경되면 화면이 자동으로 업데이트된다.
- 직접 DOM을 하나하나 조작할 필요가 없다.
- 코드가 더 읽기 쉽고 관리하기도 편리하다.
그럼에도 리엑트가 어렵게 느껴지는 이유는 리액트의 선언적인 방식이 처음엔 낯설게 느껴질 수 있고, 명령형 방식(자바스크립트로 DOM을 직접 수정하는 방식)에 익숙하기 때문이다. 하지만 선언적 사고방식에 익숙해지면 리액트를 훨씬 쉽게 사용할 수 있을 것이다.
리액트를 제대로 사용하려면 “어떻게 단계별로 만들까?”보다 “최종적으로 어떤 모습이 되어야 할까?”를 먼저 생각하는 것이 좋다. UI 모습을 선언적으로 기술하고, 데이터만 바뀌면 리액트가 알아서 화면을 바꿔준다.
바닐라 리액트의 한계
지금까지 학습한 바닐라 리액트 개발 환경은 소규모 프로젝트나 실습용으로는 충분하지만, 실제 서비스 수준의 대규모 애플리케이션 개발에는 여러 한계가 있다. 실제로 리액트 앱을 효율적으로 개발하고 운영하려면 코드 포매팅, 린팅, 번들링, 트리 쉐이킹, 코드 분할, 환경 변수 관리, 테스팅, 자동화된 빌드 및 배포 등 다양한 도구와 기능이 필요하다. 이런 도구들은 코드의 품질을 높이고, 유지 보수를 용이하게 하며, 개발 생산성을 극대화하는 데 필수적이다.
모듈 번들링
웹 애플리케이션 수요가 증가하면서 개발자는 온라인 소프트웨어를 대규모로 구축하고, 운영/유지 관리할 수 있는 보다 나은 방법이 필요해졌다. 앱에 많은 기능이 필요해질수록 모듈 종속성의 올바른 순서를 추적하고 로드하는 번거로움이 더 커진다.
복잡해질수록 코드베이스가 뒤섞이고 흩어져 개발과 유지보수가 어려워진다. 이를 해결하기 위해 코드를 모듈 단위로 분리하고, 다시 하나의 파일로 묶는 모듈 번들링 작업이 필요하다. 하지만 웹 브라우저 환경은 모듈 번들링 기능을 제공하지 않는다.
트리 쉐이킹(Tree Shaking)
애플리케이션의 어느 곳에서도 사용되지 않는 코드가 번들에 포함될 수 있다. 사용되지 않은 코드 조각은 번들 크기를 불필요하게 키우기 때문에 번들 과정에서 제거해야 한다. 이 과정을 트리 쉐이킹이라고 한다. 이 방법으로 JavaScript 번들 크기를 줄여 앱 실행 속도를 향상시킬 수 있다. 하지만 웹 브라우저 환경은 트리 쉐이킹 기능을 제공하지 않는다.
코드 분할
번들링은 매우 멋진 결과를 만들지만, 앱이 커지면 덩달아 번들 크기도 커진다. 특히 크기가 큰 써드 파티 라이브러리를 추가할 때 실수로 앱이 더욱 커져서 로드 시간이 길어질 수 있다. 이처럼 번들이 거대해지는 것을 방지하기 위한 좋은 해결 방법 중 하나는 코드를 나누는 것이다.
코드 분할은 런타임 중에 나뉘어진 청크(chunk) 파일을 동적으로 불러오는 것을 말한다. 하지만 웹 브라우저 환경은 코드 분할 기능을 제공하지 않는다.
코드 최적화 및 소스맵(Sourcemap) 생성
수많은 모듈을 번들하는 동안 수행해야 하는 코드 최적화도 필요하다. 코드 최적화는 주석, 공백 또는 긴 변수, 함수 이름 등을 모두 축소 또는 제거해 파일 크기를 크게 줄인다. 하지만 이 코드는 압축되었기 때문에 사람이 읽기 매우 어렵다.
사람이 읽기 쉽지 않은 코드는 디버깅도 쉽지 않아 코드를 추적 가능한 소스맵이 필요하다. 소스맵은 디코더처럼 작동하며 축소된 코드를 구문 분석한다. 소스맵에는 원본 파일의 행(Row)에 대한 참조도 포함되어있어 버그를 추적하기 용이하게 한다. 하지만 웹 브라우저 환경은 코드 최적화 및 소스맵 생성 기능을 기본적으로 제공하지 않는다.
결론
이처럼 바닐라 리액트 환경만으로는 실제 서비스 수준의 앱 개발에 필수적인 다양한 기능을 구현하기 어렵다. 그러므로, 프로덕션 환경에서는 Vite, Webpack, Bun 등 다양한 도구와 빌드 시스템이 필요하다. 내일부터는 리액트 빌드 환경을 구성해볼 것이다.
오늘 하루를 돌아보며
어제까지는 순수한 리액트만 사용했는데 오늘은 JSX를 사용해 보니까 확실히 너무 편리하고 읽기도 쉬워졌다. 하지만 이렇게 JSX를 사용하기 위해서는 리액트 빌드 환경을 잘 구축해야 한다. 내일 수업을 위해 예습을 해야겠다.
리액트를 제대로 사용하기 위해 선언적 사고방식에 익숙해져야겠다. 어떻게가 아니라 무엇에 집중해야 한다. 단계가 아니라 최종 모습이 중요하다. 바뀌는 데이터에 따라 리액트가 알아서 화면을 바꿔주니까 최종 UI 모습을 선언적으로 기술하는 것에 익숙해져야 한다. 많이 연습하고 많이 만들어 보자!