새로운 문법을 소개합니다!

이 글은 Learn you a React for good 시리즈의 일부입니다.

가장 유명한 프론트엔드 프레임워크를 뽑으라고 하면 어떤 프레임워크들이 나올까요?
유명한 자바스크립트 트랜딩 분석 사이트인 stateofjs의 2018년 설문의 결과는 아래와 같이 나왔습니다.

React

보아하니 리액트, 뷰, 앵귤러가 상위권에 있네요.
뷰와 앵귤러는 각각의 템플릿 문법이 존재하는 반면에 리액트는 템플릿이 없습니다.
대신, JSX라 불리는 문법이 존재합니다.
이번 글에서는 JSX에 대해 알아보도록 하겠습니다.

JSX란 무엇인가요?

앞선 글에서 제가 리액트 어플리케이션은 리액트 엘리먼트들로 구성되어 있다고 이야기를 해드렸었지요?
리액트 엘리먼트는 React.createElement 라는 메서드를 통해 생성됩니다.
하지만, 이런 방식에는 문제가 있습니다.
바로 자바스크립트 함수는 화면을 표현하는 일에 특화되있지 않다는 점인데요.
다음 예시에서도 보실 수 있으시다시피 자바스크립트 함수는 뷰나 앵귤러 같은 프레임워크들의 템플릿 문법보다 화면을 표현하는 부분에서 뒤떨어집니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
React.createElement('p', null, 'I\'m Text!');

// 보다 이게 낫지요
<p>I'm Text!</p>

// 엘리먼트들이 많으면 많을수록 둘 사이의 격차는 더 벌어집니다.
React.createElement(
'div',
{ id: 'app' },
React.createElement(
'h1',
null,
'I\'m so big!'
),
React.createElement(
'a',
{ herf: 'https://reactjs.org/' },
'Welcom to react world!'
)
);

<div id='app'>
<h1>I'm so big</h1>
<a herf='https://reactjs.org/'>Welcom to react world!</a>
</div>

이런 문제를 해결하기 위해 리액트는 자바스크립트의 문법의 연장선에 있는 새로운 문법, JSX를 만들었습니다.

NOTE:

JSX는 순수히 자바스크립트 함수(React.createElement)호출 표현식의 표현력 문제를 해결하기 위한 문법이기 때문에 표현력을 극대화시키기 위해서 마크업 언어의 문법과 똑 닮게 설계되었습니다.

왜 JSX인가요?

뷰와 앵귤러는 각각의 템플릿을 가지고 있습니다.
이는 개발자가 필수적으로 학습해야 하는 것이기 때문에 결과적으로 학습 비용을 늘리게 됩니다.
또한 그 시멘틱(언어의 의미론)또한 자체적으로 구성되었기 때문에 자바스크립트 개발자들이 필히 이질감을 느끼게 되어 있습니다.
(예를 들어, 앵귤러에서는 템플릿에서 몇몇 유형의 코드를 작성하는 것을 금지하고 있습니다.)

하지만 JSX는 자바스크립트의 문법의 연장선에 있는 문법이기 때문에 자바스크립트의 시멘틱에서 벗어나지 않으며,
React.createElement라는 메서드를 호출하는 문법적 설탕(syntactic sugar)이므로 학습이 강제되지도 않습니다.

또한 템플릿을 통해 화면을 표현한다 하여도 결국에는 이벤트 핸들링 등을 위해 자바스크립트 코드와 템플릿을 결합시키게 됩니다.
이 때, 둘 사이의 결합도는 절대 낮다고 할 수 없는데요, 양방향 바인딩을 사용하는 경우에는 둘의 결합도는 가장 높은 단계인 내용 결합도(Content Coupling)에 해당하게 됩니다.
양방향 바인딩이 아니어도, 이벤트 리스너를 등록하는 일을 해야 하기 때문에 최소한 제어 결합도(Control Coupling)에 해당하게 됩니다.
리액트는 이렇게 억지스러운 관심사의 분리를 실천하기 보다는 차라리 템플릿 + 로직을 합친 추상화의 단위인 컴포넌트를 형성하기로 결정하였습니다.
템플릿(마크업)과 로직을 합치는 과정에서, 리액트는 마크업을 자연스럽게 코드에 녹일 문법이 필요했고(React.createElement는 마크업처럼 보이지 않잖아요.), 그것이 바로 JSX 입니다.

React.createElement와 친해지기

JSX는 React.createElement를 호출하는 표현식의 문법적 설탕이기 때문에 React.createElement 없이는 설명해드릴 수 없는 문법입니다.
그러니, 잠시 React.createElement에 대해 알아보도록 하겠습니다.

여러 번 말씀드리지만, React.createElement는 리액트 엘리먼트를 생성하기 위해 사용되는 메서드입니다.
리액트 엘리먼트를 생성하려면 무엇이 필요했지요? 네 그렇습니다, 컴포넌트와 그 컴포넌트에게 줄 props가 필요합니다.
그래서 React.createElement의 콜 시그너쳐(call signature)는 대략 이렇게 생겼습니다.

1
2
3
4
// 타입스크립트 버전
function createElement<P extends object>(type: string | Component<P>, props?: P, ...children: Array<ReactElement>): ReactElement
// 자바스크립트 버전
function createElement(type, props, ...children)

아직 감이 잘 안 오시나요?
걱정 마세요, 지금부터 자세히 설명해드릴겁니다.

먼저, React.createElementtype이라는 매개변수로 문자열이나 컴포넌트를 받는데요.
문자열의 경우 그 문자열에 일치하는 DOM 엘리먼트를 생성하는 생성자를 암시하는 것으로 인식하고, 컴포넌트의 경우 그냥 컴포넌트로 받아들입니다.
왜 문자열로 받냐구요? 왜냐하면 React.createElement(HTMLInputElement, { type: 'text' }); 처럼 작성하면 코드가 길어질 뿐더러, HTMLInputElement 같은 생성자 함수들은 일반적으로 호출할 수 없는 생성자 함수이기 때문입니다(그래서 DOM을 다룰 때 document.createElement와 문자열을 사용하지요.).

1
2
3
React.createElement('div', null); // DOM의 div 엘리먼트 생성자로 인식

React.createElement(MyComponent, null); // 컴포넌트로 인식

다음으로 props라는 매개변수에 객체나 null 값을 받는데요,
type이 문자열인 경우에는 나중에 DOM 트리에 지금 만든 리액트 엘리먼트를 DOM 엘리먼트로서 삽입할 때 props에 있는 프로퍼티들을 삽입할 DOM 엘리먼트의 어트리뷰트로서 설정되기 위해 다른 곳에 저장됩니다.
type이 컴포넌트인 경우에는 나중에 컴포넌트를 랜더링 할 때 컴포넌트에 props로서 주어지기 위해 다른 곳에 저장됩니다.

마지막으로는 children이라는 매개변수를 통해 리액트 엘리먼트의 자식들을 받는데요,
type이 문자열인 경우에는 props의 경우와 마찬가지로 나중에 DOM 엘리먼트의 자식으로 설정되기 위해 따로 저장되며,
type이 컴포넌트인 경우에는 props에서 받은 객체의 children 프로퍼티에 담기게 됩니다(props.children = children).

이 모든 작업이 끝나면 React.createElement는 아까 언급한 다른 곳에 저장된 값들과 컴포넌트 등 랜더링 할 때 사용할 데이터들을 담고 있는 리액트 엘리먼트를 반환합니다.

NOTE:

propsnull인 경우에는 리액트가 빈 객체인 props를 생성하고, 그 객체에 children 매개변수의 값을 담습니다.
또한, children 매개변수의 값의 길이가 1인 경우, children 매개변수에 들어있는 유일한 값을 꺼내서 props객체의 children 프로퍼티에 담습니다(props.children = children[0]).

NOTE:

React.createElement로 리액트 엘리먼트를 만들 때 사용한 Boolean, Null, Undefined 타입의 값들은 랜더링되지 않습니다.
예를 들어, React.createElement('p', null, true, 'hi', null)<p>hi</p> 로 랜더링 됩니다.

JSX와의 첫 만남

자, 그럼 이제 본격적으로 JSX에 대해 알아보도록 하겠습니다.

1
<p id='소개'>저는 JSX 입니다.</p>

짠, 이게 JSX 로 작성된 코드입니다.

JSX는 바벨과 같은 컴파일러에 의해 컴파일되는데요, 위의 코드는 아래와 같이 컴파일됩니다.

1
React.createElement('p', { id: '소개' }, '저는 JSX 입니다.');

어떤가요? 감이 오시나요?
여러분들의 이해를 돕기 위해 제가 그림을 그려 보았습니다.

React

문자로 정리해 드리자면, JSX는 아래와 같은 형식입니다.

1
<type propKey1='value1' propKey2='value2' >...children</type>

이 코드는 아래와 같이 컴파일됩니다.

1
React.createElement('type', { propKey1: 'value1', propKey2: 'value2' }, '...children');

NOTE:

JSX 코드가 어떻게 컴파일되는 지 직접 시험해보고 싶으신 분들은 여기서 시험해보시면 됩니다.

JSX는 표현식입니다!

이제 우리는 JSX가 무엇인지, 왜 만들어졌는지 알고 있습니다!
근데 JSX는 표현식일까요 구문일까요? 아니면 제 3의 어떤 것일까요?
JSX는 표현식입니다!
JSX가 React.createElement를 호출하는 표현식을 대체하는 문법 설탕이라는 점을 제가 이야기 한 것을 기억하시나요? 그게 바로 이유입니다.
JSX는 순수히 React.createElement를 호출하는 표현식을 대체해야 하기 때문에 함수 호출 표현식의 성질을 그대로 가지게 된 것이지요.

1
2
3
4
5
6
7
// 아래 둘이 의미상 같기에
<p>저는 아래의 코드와 같습니다.</p>
React.createElement('p', null, '저는 아래의 코드와 같습니다.');

// 아래 둘도 의미상 같습니다.
const p = React.createElement('p', null, '저는 아래의 코드와 같습니다.');
const p = <p>저는 아래의 코드와 같습니다.</p>;

JSX에 표현식 삽입하기

React.createElement를 사용하면 React.createElement('p', null, Date.now()) 처럼 표현식을 사용할 수 있습니다.
그렇다면 JSX에서는 어떻게 해야 할까요? React.createElement를 대체하는 문법이라면 표현식 사용도 지원해야 하지 않을까요?
네, 그런 이유로 JSX 안에 표현식을 삽입하는 방법이 존재합니다. 바로 {}로 표현식을 감싸는 방법인데요. 백문이 불여일견, 설명해드리기 전에 코드를 먼저 보여드리도록 하겠습니다.

1
2
3
<p id={generateId()} className={'time ' + fontSize}>
{Date.now()}
</p>

{}로 감싼 표현식은 JSX의 두 자리에서 쓰일 수 있습니다.
그 두 자리는 바로 props의 프로퍼티를 정의하는 부분(propName=propValue)에서 propValue의 자리와 리액트 엘리먼트의 자식을 나열하는 자리(시작 태그와 종료 태그의 사이, <tag></tag> 사이)입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// Good :)
<input value={defaultValue} />
<p>{words}</p>

// Bad :(
<{tagName} />
<input {propName}={propValue} />

// 임의의 prop을 만들고 싶은 경우에는 아래와 같이 하시면 됩니다.
const props = {
[propName]: propValue,
};
<input id='my-input' {...props} /> // React.createElement('input', { id: 'my-input', ...props });

표현식을 JSX에 삽입하려면 {}로 감싸야 하지만, 문자열 리터럴을 삽입하는 경우에는 {}로 감싸지 않아도 됩니다(JSX가 마크업 언어의 문법을 존중하기 때문입니다.).

1
2
<input type='email' placeholder='이메일을 입력해 주세요' />
<p>저는 텍스트입니다.</p>

props의 프로퍼티를 정의할 때 true를 값으로 넣어야 하는 경우에는 true를 생략할 수 있습니다.

1
<input readonly /> // React.createElement('input', { readonly: true }); 로 컴파일됩니다.

자, 지금까지 JSX에 대해 알아보았습니다.
이번 포스트는 퀄리티가 조금 떨어지는 것 같아 아쉽네요, 조만간 다시 작성해야겠습니다.

그럼 저는 다음 글인 컴포넌트 길들이기로 다시 찾아뵙도록 하겠습니다.
좋은 하루 되세요 :)))

공유하기