모던 리액트 Deep Dive 02장 리액트 핵심 요소 깊게 살펴보기의 JSX파트를 정리한 글입니다.
1. JSX란?
JSX 내부에 트리 구조로 표현하고 싶은 다양한 것들을 작성해 두고, 이 JSX를 트랜스파일이라는 과정을 거쳐 자바스크립트(ECMAScript)가 이해할 수 있는 코드로 변경하는 것이 목표인 문법이다.
2. JSX의 구성요소
JSX는 다음과 같은 4가지의 컴포넌트를 기반으로 구성돼 있다.
1. JSXElement
2. JSXAttributes
3. JSXChildren
4. JSXStrings
2-1. JSXElement
JSX를 구성하는 가장 기본 요소로, HTML의 요소(element)와 비슷한 역할을 한다. 이는 다음과 같은 형태 중 하나여야 한다.
1. JSXOpeningElement, JSXClosingElement: 열고 닫는 요소이다. 각각의 요소가 같은 단계에서 선언돼 있어야 한다.
2. JSXSelfClosingElement: 요소가 시작되고, 스스로 종료되는 형태를 의미한다.
3. JSXFragment: 아무런 요소가 없는 형태를 의미한다.
JSXElement의 요소 이름에는 어떤 것이 사용될 수 있을까? 이름으로 사용될 수 있는 것을 JSXElementName이라고 하며 다음과 같은 것들이 있다.
1. JSXIdentifier: JSX 내부에서 사용할 수 있는 식별자를 의미한다. 자바스크립 식별자 규칙과 동일하다.
2. JSXNamespacedName: `:`을 통해 서로 다른 식별자를 이어주는 방법을 의미한다.
3. JSXMemberExpression: `.`을 여러 개 이어서 다른 식별자를 이어주는 것을 의미한다.
JSXNamespacedName, JSXMemberExpression은 리액트에서 사용하지 않는다.
2-2. JSXAttributes
JSXElement에 부여할 수 있는 속성을 의미한다. JSXAttributes은 두 가지 형태로 표현할 수 있다.
첫 번째는 JSXSpreadAttributes이다. 자바스크립트의 전개 연산자와 동일한 역할을 한다고 볼 수 있다.
두 번째는 JSXAttribute이다. 속성을 나타내는 키와 값으로 짝을 이루어서 표현한다. 키는 JSXAttributeName, 값은 JSXAttributeValue로 불린다. JSXAttributeValue은 다음 중 하나를 만족해야 한다.
1. "큰따옴표로 구성된 문자열"
2. '작은따옴표로 구성된 문자열'
3. { AssignmentExpression }: 자바스크립트에서 변수에 값을 넣을 수 있는 표현식을 의미한다.
4. JSXElement: 다른 JSX 요소를 의미한다.
5. JSXFragment: 비어 있는 형태의 <></>을 의미한다.
2-3. JSXChildren
JSXElement의 자식 값을 나타낸다. JSX은 트리 구조를 나타내기 위해 만들어졌기 때문에 JSX로 부모와 자식 관계를 나타낼 수 있다. 이를 JSXChildren이라고 한다.
JSXChildren은 JSXChild으로 이루어져 있다. JSXChild은 0개 이상 가질 수 있다. JSXChild엔 다음과 같은 것들이 있다.
1. JSXText: {, <, >, }을 제외한 문자열을 의미한다.
2. JSXElement: 다른 JSX 요소를 의미한다.
3. JSXFragment: 빈 JSX 요소인 <></>을 의미한다.
4. { JSXChildExpression (optional) }: 아래의 사진과 같은 것을 의미한다.
2-4. JSXStrings
HTML에서 사용 가능한 문자열은 모두 JSXStrings에서도 가능하다. "큰따옴표로 구성된 문자열", '작은따옴표로 구성된 문자열' 혹은 JSXText를 의미한다.
3. JSX는 어떻게 자바스크립트로 변환될까?
결론부터 말하자면 JSX 반환값은 React.createElement로 귀결된다. 아래는 React.createElement의 타입이다.
정리하자면 다음과 같다.
1. JSXElement를 첫 번째 인수로 선언해 요소를 정의한다.
2. 옵셔널인 JSXChildren, JSXAttributes, JSXStrings는 이후 인수로 넘겨주어 처리한다.
이를 활용하면 props 통해 JSXElement만 다르게 하고 싶은 경우 중복 코드를 최소화할 수 있다.(물론, JSX만 사용하던 지금의 내 입장에선 가독성 측면에선 그냥 JSX를 사용하는 것이 더 좋아 보인다.)
예를 들어 다음과 같은 코드를 살펴보자.
function TextOrHeading({
isHeading,
children,
}: PropsWithChildren<{ isHeading: boolean }>) {
return isHeading ? (
<h1 className="text">{children}</h1>
) : (
<span className="text">{children}</span>
);
}
isHeading에 따라 `h1` 태그인지 `span` 태그인지 달라진다. 하지만 그 외의 것들은 모두 같다. 같은 것의 중복을 줄이기 위해 React.createElement를 사용하면 다음과 같이 리팩터링을 할 수 있다.
function TexOrHeading({
isHeading,
children,
}: PropsWithChildren<{
isHeading: boolean;
}>) {
return React.createElement(
isHeading ? 'h1' : 'span',
{ className: 'text' },
children
);
}
React.createElement의 첫 번째 요소인 JSXElement만 삼항연산자로 구분하고 있다. 그 외의 것들은 달라지지 않으므로 구분할 필요가 없다.
4. 정리
컴포넌트에서 마크업을 JSX를 통해 쉽게 할 수 있다. 좋다. 하지만 JSX에 자바스크립트 문법이 많이 뒤섞여 있으면 가독성을 해치기 때문에 이를 조심하자.
삼항연산자를 최소화하고 복잡한 로직은 JSX내부가 아니라 외부에서 실행하여 결과 값을 가져오자.