React-logo
JSX는 React 엘리먼트를, React 엘리먼트는 브라우저 DOM을 만든다

지난 시간에는 React 설치 create-react-app (2022년 기준)에서 create-react-app을 이용해 리액트(React)를 설치하는 법과 리액트의 기본 폴더 구조를 알아보았습니다. 또 각 폴더에 속한 파일의 종류와 파일의 역할도 함께 살펴보았죠. 이번 시간에는 리액트의 문법인 JSX와 JSX가 생성하는 리액트 엘리먼트(element, 요소)의 개념을 알아보겠습니다.

JSX

개념

우리는 다음과 같이 h1 태그에 id가 "greeting"이고 내용은 "Hello, world!"인 HTML 엘리먼트를 만들려고 합니다.

html
<h1 id="greeting">Hello, world!</h1>

이 결과물을 만들려면 리액트에서 두 가지 방법을 이용할 수 있습니다. 아래 두 코드를 비교해보세요.

jsx
const element = <h1 id="greeting">Hello, world!</h1> // 코드 1
jsx
React.createElement("h1", { id: "greeting" }, "Hello, world!") // 코드 2

두 코드 중 어느 코드가 더 직관적이고 이해하기 쉬운가요? 아마도 첫 번째 코드일 것입니다. 첫 번째 코드는 JSX로 작성한 코드입니다. 두 번째 코드는 JSX를 이용하지 않았고요.

이처럼 JSX는 HTML은 아니지만 HTML과 유사하다 보니 JSX를 이용하면 더 편리하게 HTML을 작성할 수 있습니다. JSX는 어려운 무언가가 아니라 자바스크립트(JavaScript) 코드 안에서 바로 HTML을 작성할 수 있도록 도와주는 리액트의 문법이자 자바스크립트의 확장 기능입니다. 다만 JSX는 HTML 형식이 아닌 XML 형식에 따라 작성합니다. 특히 다음 시간에 다룰 컴포넌트를 작성할 때 말이죠. 그래서 JSX라는 줄임말도 JavaScript XML에서 나왔습니다.

리액트에서는 코드1과 같이 JSX로 코드를 작성하면 브라우저를 실행하기 전에 코드2처럼 일반 자바스크립트 형태의 코드로 바꿔줍니다. 코드를 오래된 브라우저와 호환하는 자바스크립트로 트랜스파일(transpile)하는 바벨(Babel) 라이브러리를 통해서요. 우리는 JSX를 쓰고 있다고 생각하지만, 실제로는 함수를 호출하고 있는 것입니다.

다만 코드2는 코드1 보다 복잡합니다. 우리는 JSX를 이용한 첫 번째 코드처럼 더 사용하기 편한 방법을 선택하면 됩니다.

규칙

JSX를 사용할 때는 몇 가지 지켜야 할 규칙이 있습니다.

표기법

JSX에서는 변수 또는 함수를 선언할 때 그리고 HTML의 style 속성을 기술할 때 카멜(camel) 표기법을 사용합니다. 예시는 다음과 같습니다.

  • 변수: const myElement
  • 함수: function sayHello()
  • HTML 태그 스타일 속성: <h1 style={{ backgroundColor: 'blue' }}>파란색</h1>

다만 React 컴포넌트와 props 개념 이해하기에서 알아볼 컴포넌트를 기술할 때는 맨 앞 글자는 대문자로 기술해야 합니다.

  • 컴포넌트: <BookList />, <TopNavbar />

또한 HTML 태그의 class 속성을 JSX에서는 className으로 변경해서 사용해야 합니다.

중괄호

JSX에서는 자바스크립트 표현식을 중괄호({}) 안에 표현합니다. 함수를 JSX 내에서 유효한 표현식으로 나타내기 위해서는 해당 코드를 중괄호로 감싸주어야 합니다. 예제 코드는 다음과 같습니다.

jsx
function sayHello(name) {
return `Hello, ${name}!`
}
const name = "우영우"
const element = (
<h1>
Hello, {sayHello(name)}! // 중괄호 사용
</h1>
)
ReactDOM.render(
element,
document.getElementById('root')
);

HTML 속성을 정의할 때도 속성에 할당되는 변수를 중괄호로 감싸서 자바스크립트 표현식을 나타냅니다.

jsx
const style = {
backgroundColor: 'blue',
fontSize: '1.25rem'
}
const url = "www.snugarchive.com"
const element = (
<h1 style={style}>Hello, world!</h1>
<img src={url}></img>
)
ReactDOM.render(
element,
document.getElementById('root')
);

부모 요소 반환

JSX에서 HTML 태그를 반환할 때는 반드시 HTML 태그를 묶어주는 단 하나의 부모 태그가 있어야 합니다.

jsx
return (
<div>
<h1>Hello, world!</h1>
<p>My name is Apple.</p>
</div>
)

만약 다음과 같이 부모 태그를 사용하지 않으면 에러가 발생합니다.

jsx
return (
<h1>Hello, world!</h1>
<p>My name is Apple.</p>
)

부모 태그로는 꼭 특정한 HTML 태그를 사용해야 하는 것은 아닙니다. 아래와 같이 Fragment라고 불리는 빈 태그 모양을 사용할 수도 있습니다.

jsx
return (
<>
<h1>Hello, world!</h1>
<p>My name is Apple.</p>
</>
)

조건부 렌더링

JSX 내부에서는 삼항 연산자(ternary opeator)를 사용해야 합니다. if 문이나 for 문은 값으로 평가되는 표현식이 아니기 때문에 JSX 내부에서 사용할 수 없습니다.

jsx
const authenticated = true
return (
<>
{ authenticated ?
<h1>Hello, world!</h1> :
<h1>Hello, stranger!</h1>
}
</>
)

위 코드를 AND 연산자(&&)을 이용해서 다음과 같이 표현할 수도 있습니다.

jsx
const authenticated = true
return (
<>
{ authenticated && <h1>Hello, world!</h1> }
</>
)

값이 존재하지 않을 때는 OR 연산자(||)를 이용해 초기값을 지정해 줄 수도 있습니다.

jsx
const authenticated = true
const name = "우영우"
return (
<>
{ <h1>Hello, {name}!</h1> || <h1>"Hello, world!"</h1> }
</>
)

지금까지 간단히 JSX의 문법 규칙을 알아보았습니다. 컴포넌트를 이용해서 JSX를 작성하는 방법은 React 컴포넌트와 props 개념 이해하기에서 다루겠습니다. 다시 JSX 개념에서 다룬 코드1과 코드2로 돌아와 보겠습니다.

코드1 또는 코드2를 작성하고 나면 리액트에서 어떻게 화면에 "Hello, World!"를 보여줄 수 있을까요? 답은 리액트 엘리먼트(elements, 요소)에 있습니다. 우리가 이 두 코드를 이용해서 만든 것은 리액트 엘리먼트입니다. 이 리액트 엘리먼트를 렌더링하면 브라우저 화면에 "Hello, world!"가 나타납니다. 그럼 리액트 엘리먼트란 무엇일까요?

리액트 엘리먼트

개념

리액트 엘리먼트는 리액트 앱을 이루는 가장 기본적인 단위입니다. 리액트 엘리먼트는 렌더링 과정이 필요한 HTML 엘리먼트와 다르게 쉽게 만들 수 있습니다. 위에서 살펴본 코드1처럼 개발자가 작성하기만 하면 되죠. 개발자와 사용자 입장에서 리액트 엘리먼트는 우리가 화면에서 보이는 내용을 기술합니다. 그럼 리액트 입장에서 리액트 엘리먼트는 어떤 역할을 할까요?

리액트 엘리먼트는 리액트에게 브라우저 DOM(Document Object Model, 문서 객체 모델)을 만드는 방법을 알려주는 명령입니다. 브라우저 DOM이란 웹 페이지의 사용자 인터페이스(user interface, UI)입니다. 리액트 엘리먼트는 리액트가 브라우저 화면에 실제로 어떤 DOM 노드를 생성할지 알려줍니다.

가상DOM-브라우저DOM-관계
가상DOM과 브라우저 DOM의 관계도 | Snug Archive

DOM 구조는 매우 복잡합니다. 리액트는 어떻게 이 복잡한 DOM을 쉽게 제어할 수 있는 것일까요? 리액트는 가상 DOM(virtual DOM)이라는 도구로 이 문제를 해결합니다. 리액트는 리액트 엘리먼트를 이용해 실제 브라우저 DOM에 대응하는 가상 DOM을 구현합니다.

가상 DOM은 자바스크립트 객체입니다. 가장 DOM은 리액트 엘리먼트로 이루어져 있고 브라우저 DOM은 DOM 엘리먼트로 이루어져 있죠. 여기서 "가상"이라는 것은 실제로 구현된 브라우저 DOM이 아니라, 메모리상에만 존재한다는 의미입니다. 브라우저 DOM은 실제 대상이지만 가상 DOM은 실제로 존재하지는 않으나 브라우저 DOM을 구현하기 위해 리액트에서 만든 가상 도구입니다.

가상DOM
가상 DOM은 브라우저 DOM을 구현하기 위해 메모리상에만 있는 가상의 대상 Shubham Dhage | Unsplash

그런데 이 가상 DOM은 기특한 것이 실제 DOM을 구현하기 전에 미리 변화를 준비합니다. 개발자가 리액트 엘리먼트를 이용해 가상 DOM에서 변경하고 싶은 부분만 손보면, 리액트는 변경된 부분에 대응하는 실제 DOM 부분만 수정합니다. 그래서 리액트와 같은 도구로 만드는 단일 웹 페이지(Single Page Application, SPA)에서는 웹 서버에 HTML 문서를 응답받아 단 한 번만 페이지를 렌더링하면, 이후에는 필요한 부분만 바꿔서 실제 DOM을 갱신하면 됩니다.

이러한 작동 방식은 결과적으로 사용자에게 반응성이 빠르고 웹 페이지를 이동할 때 부드럽게 넘겨지는 경험을 선사합니다. 그래서 리액트를 이용하면 기존의 전통적인 HTML로 웹 페이지를 구현하는 방식보다 더 효율적이죠.

정리해보면 리액트 엘리먼트는 브라우저 DOM을 만들기 위해 개발자와 브라우저 DOM을 이어주는 오작교입니다. 개발자는 리액트 엘리먼트를 이용해 브라우저 DOM의 생김새를 기술하고, 리액트 엘리먼트는 리액트에 브라우저 DOM의 생김새가 어떤지 알려주니까요.

지금까지 리액트 엘리먼트의 개념을 알아보았습니다. 우리가 JSX로 코드를 작성하면 리액트는 이를 React.createElement 함수로 바꿉니다. 그리고 이 함수는 값을 반환하는데 반환되는 값이 바로 리액트 엘리먼트 객체입니다. 리액트 엘리먼트 객체는 어떻게 생겼을까요?

구성 요소

React.createElement 함수가 반환한 리액트 엘리먼트 객체는 다음과 같이 생겼습니다[1].

jsx
{
$$typeof: Symbol(React.element),
"type": "h1",
"key": null,
"ref": null,
"props": {id: "greeting", children: "Hello, world!"},
"_owner": null,
"_store": {}
}

리액트 엘리먼트에는 위와 같이 여러 필드가 있습니다. 우리가 기술한 내용은 type과 props에 담깁니다. type 프로퍼티는 HTML이나 SVG 엘리먼트의 타입을 지정합니다. 우리는 h1 태그를 사용했기 때문에 h1이라고 기술되었죠. props는 DOM 엘리먼트를 만드는데 필요한 데이터 또는 자식 엘리먼트를 렌더링(rendering)합니다. props의 내부 프로퍼티인 children을 이용해 우리는 "Hello, world!"라는 텍스트를 렌더링 할 것입니다. props는 React 컴포넌트와 props 개념 이해하기에서 더 자세히 다루겠습니다.

작동 방식

이 렌더링 작업을 하려면 우리는 ReactDOM 라이브러리를 불러와야 합니다. ReactDOM은 UI를 실제로 브라우저에 렌더링할 때 사용하는 라이브러리입니다. ReactDOM의 render 함수는 리액트 엘리먼트와 해당 엘리먼트의 모든 자식 엘리먼트를 루트(root) DOM 노드에 렌더링해줍니다.

루트 DOM 노드는 public 폴더에 있는 index.html 파일의 <div id="root"></div>를 말합니다. 만들어진 리액트 엘리먼트는 모두 이 노드 안에 들어가기 때문에 루트라는 이름이 붙여졌습니다. 리액트의 index.js 파일을 아래와 같이 설정하면 브라우저 화면에 "Hello, world!"를 출력할 수 있습니다.

jsx
import ReactDOM from 'react-dom'
const element = <h1>Hello, world!</h1> // 코드1
ReactDOM.render(element, document.getElementById('root'))

지금까지 우리는 JSX와 리액트 엘리먼트의 개념과 관계를 알아보았습니다. 마지막으로 이번 시간에 알아본 개념을 이용해서 코드를 작성해보겠습니다. 예제는 아래에 제시되어 있습니다. 다음 시간에는 React 컴포넌트와 props 개념 이해하기에서 컴포넌트와 props의 개념을 알아보겠습니다. 모두 수고 많으셨습니다.

jsx
const user = {
firstName: '영우'
lastName: '우'
}
function formatName(user) {
return `${user.firstName}${user.lastName}`
}
function introduce(user) {
const name = formatName(user)
return <h1>제 이름은 똑바로 읽어도 거꾸로 읽어도 ${name}입니다.
기러기, 토마토, 스위스, 인도인, 별똥별, ${name}.</h1>
}
const element = (
<main>
{ user ?
<h1>Hello, {introduce(user)}.</h1> :
<h1>Hello, stranger.</h1>
}
</main>
)
ReactDOM.render(
element, document.getElementById('root')
)

참고 문헌


  1. React.createElement의 세 개의 인자는 각각 만들려는 리액트 엘리먼트의 타입과, 엘리먼트의 프로퍼티, 그리고 여는 태그와 닫는 태그 사이에 들어갈 자식 노드를 정의
...

©2022 Snug Archive. All rights reserved.

Contact me at snugarchive@gmail.com.