Writing Markup with JSX

김동현·2026년 3월 15일

title: JSX로 마크업 작성하기

JSX는 자바스크립트 파일 안에 HTML과 유사한 마크업을 작성할 수 있게 해주는 자바스크립트 구문 확장(syntax extension)이에요. 컴포넌트를 작성하는 다른 방법들도 있지만, 대부분의 리액트 개발자들은 JSX 특유의 간결함을 선호하며, 실제로 대부분의 코드베이스에서 JSX를 채택하여 사용하고 있답니다.

이 장에서 배울 내용:

  • 리액트가 마크업과 렌더링 로직을 혼합해서 사용하는 이유
  • JSX가 HTML과 어떻게 다른지
  • JSX를 사용해 정보를 화면에 표시하는 방법

JSX: 자바스크립트 안에 마크업 넣기 {/jsx-putting-markup-into-javascript/}

웹은 본래 HTML, CSS, 그리고 자바스크립트를 기반으로 구축되어 왔어요. 수년 동안 웹 개발자들은 콘텐츠는 HTML에, 디자인은 CSS에, 로직은 자바스크립트에 분리하여 유지해 왔죠. 종종 이들을 아예 분리된 파일로 관리하기도 했고요! 즉, 콘텐츠는 HTML 내부에서 마크업된 반면, 페이지의 로직은 자바스크립트라는 별도의 공간에 살고 있었습니다.

👨‍🏫 강사의 보충 설명:
예전에는 이를 "관심사의 분리(Separation of Concerns)"라고 부르며, 파일의 종류(HTML, CSS, JS)에 따라 역할을 엄격하게 나누는 것을 좋은 구조라고 생각했어요. 화면의 뼈대를 그리는 사람과 그 화면에 생명을 불어넣는 코드를 완전히 격리했던 것이죠.

하지만 웹이 점점 더 상호작용(interactive)이 많아짐에 따라, 로직이 콘텐츠를 결정하는 경우가 많아졌어요. 자바스크립트가 HTML의 내용을 통제하고 책임지게 된 것이죠! 이것이 바로 리액트에서 렌더링 로직과 마크업이 '컴포넌트(components)'라는 한 곳에서 함께 살게 된 이유랍니다.

👨‍🏫 강사의 보충 설명:

리액트에서는 "파일의 종류"로 관심사를 나누지 않아요. 그 대신, UI를 구성하는 하나의 단위인 "컴포넌트" 단위로 관심사를 나눕니다. 버튼 하나를 만들더라도 그 버튼의 모양(HTML)과 클릭했을 때의 동작(JS)은 서로 매우 밀접한 관련이 있기 때문에 한데 묶어두는 것이죠.

버튼의 렌더링 로직과 마크업을 같이 보관하면, 코드를 수정할 때마다 이 둘이 항상 서로 일치하도록(sync) 유지할 수 있어요. 반대로, 버튼의 마크업과 사이드바의 마크업처럼 서로 관련이 없는 세부 사항들은 서로 격리되므로, 각자를 독립적으로 안전하게 변경할 수 있는 장점이 생깁니다.

각 리액트 컴포넌트는 사실 리액트가 브라우저에 렌더링할 마크업을 일부 포함하고 있는 자바스크립트 '함수(function)'예요. 리액트 컴포넌트는 이 마크업을 표현하기 위해 JSX라는 구문 확장을 사용합니다. JSX는 HTML과 아주 비슷하게 생겼지만, 문법적으로 조금 더 엄격하고 동적인 정보를 표시할 수 있다는 특징이 있어요. 이를 이해하는 가장 좋은 방법은, 기존의 평범한 HTML 마크업을 JSX 마크업으로 직접 변환해 보는 거예요.

JSX와 React는 서로 다른 별개의 개념이에요. 이 둘은 종종 함께 짝을 이루어 사용되지만, 서로 독립적으로 사용하는 것도 가능하답니다. JSX는 자바스크립트의 구문 확장일 뿐이고, React는 자바스크립트 라이브러리라는 점을 기억해두세요.

HTML을 JSX로 변환하기 {/converting-html-to-jsx/}

여러분이 (완벽하게 유효한) 다음과 같은 HTML 코드를 가지고 있다고 가정해 볼까요?

<h1>Hedy Lamarr's Todos</h1>
<img 
  src="[https://i.imgur.com/yXOvdOSs.jpg](https://i.imgur.com/yXOvdOSs.jpg)" 
  alt="Hedy Lamarr" 
  class="photo"
>
<ul>
    <li>Invent new traffic lights
    <li>Rehearse a movie scene
    <li>Improve the spectrum technology
</ul>

그리고 이 코드를 여러분이 만든 컴포넌트 안에 넣고 싶다고 해보죠:

export default function TodoList() {
  return (
    // ???
  )
}

만약 이 HTML 코드를 있는 그대로 복사해서 붙여넣기 한다면, 아마 제대로 동작하지 않을 거예요.

export default function TodoList() {
  return (
    // 이 코드는 제대로 동작하지 않습니다!
    <h1>Hedy Lamarr's Todos</h1>
    <img 
      src="[https://i.imgur.com/yXOvdOSs.jpg](https://i.imgur.com/yXOvdOSs.jpg)" 
      alt="Hedy Lamarr" 
      class="photo"
    >
    <ul>
      <li>Invent new traffic lights
      <li>Rehearse a movie scene
      <li>Improve the spectrum technology
    </ul>
  );
}
img { height: 90px }

동작하지 않는 이유는 JSX가 기존 HTML보다 조금 더 엄격하고, 지켜야 할 몇 가지 규칙이 더 있기 때문이에요! 화면에 나타난 에러 메시지를 읽어보면 마크업을 어떻게 고쳐야 할지 안내해 줄 겁니다. 아니면 아래의 가이드를 차근차근 따라오셔도 좋습니다.

대부분의 경우 리액트 화면에 나타나는 에러 메시지가 문제의 원인을 찾는 데 큰 도움을 줍니다. 막히는 부분이 있다면 꼭 에러 메시지를 천천히 읽어보세요!

JSX의 규칙들 {/the-rules-of-jsx/}

1. 단일 루트(Root) 엘리먼트를 반환하세요 {/1-return-a-single-root-element/}

컴포넌트에서 여러 개의 엘리먼트를 반환하고 싶다면, 반드시 그것들을 하나의 부모 태그로 감싸주어야 합니다.

예를 들어, 가장 흔하게는 <div>를 사용해서 감쌀 수 있어요:

<div>
  <h1>Hedy Lamarr's Todos</h1>
  <img 
    src="[https://i.imgur.com/yXOvdOSs.jpg](https://i.imgur.com/yXOvdOSs.jpg)" 
    alt="Hedy Lamarr" 
    class="photo"
  >
  <ul>
    ...
  </ul>
</div>

만약 결과물(마크업)에 의미 없는 <div> 태그를 추가하기 싫다면, <></> 기호를 대신 사용할 수 있습니다:

<>
  <h1>Hedy Lamarr's Todos</h1>
  <img 
    src="[https://i.imgur.com/yXOvdOSs.jpg](https://i.imgur.com/yXOvdOSs.jpg)" 
    alt="Hedy Lamarr" 
    class="photo"
  >
  <ul>
    ...
  </ul>
</>

이렇게 비어 있는 태그를 Fragment(프래그먼트)라고 부릅니다. 프래그먼트를 사용하면 브라우저의 HTML 트리에 어떠한 흔적(불필요한 DOM 노드)도 남기지 않은 채로 여러 요소들을 하나로 예쁘게 그룹화할 수 있답니다.

왜 여러 개의 JSX 태그는 묶어주어야 할까요? {/why-do-multiple-jsx-tags-need-to-be-wrapped/}

👨‍🏫 강사의 보충 설명:
JSX는 겉보기엔 HTML처럼 생겼지만, 그 내부 동작(under the hood)을 살펴보면 평범한 자바스크립트 객체(plain JavaScript objects)로 변환됩니다. 자바스크립트 문법을 생각해 볼까요? 하나의 함수에서 두 개의 객체를 반환하고 싶을 때 배열로 감싸지 않고서는 불가능하죠? 이와 같은 맥락으로, 두 개의 JSX 태그 역시 다른 부모 태그나 프래그먼트로 감싸주지 않으면 함수가 올바르게 반환할 수 없는 것이랍니다.

2. 모든 태그를 닫아주세요 {/2-close-all-the-tags/}

JSX에서는 모든 태그가 명시적으로 닫혀 있어야만 해요. HTML에서는 닫는 태그를 생략하기도 하는 <img> 같은 단일 태그(self-closing tags)는 반드시 <img />의 형태로 닫아주어야 하고, <li>oranges처럼 무언가를 감싸는 태그 역시 반드시 <li>oranges</li>로 끝에 닫는 태그를 명시해주어야 합니다.

규칙에 맞게 Hedy Lamarr의 이미지와 리스트 항목들의 태그를 제대로 닫아주면 아래와 같은 모습이 됩니다:

<>
  <img 
    src="[https://i.imgur.com/yXOvdOSs.jpg](https://i.imgur.com/yXOvdOSs.jpg)" 
    alt="Hedy Lamarr" 
    class="photo"
   />
  <ul>
    <li>Invent new traffic lights</li>
    <li>Rehearse a movie scene</li>
    <li>Improve the spectrum technology</li>
  </ul>
</>

👨‍🏫 강사의 시각 자료:
(위 코드에서 불러오는 이미지는 다음과 같이 화면에 나타나게 됩니다.)
Hedy Lamarr

3. 모든 것을 대부분의 것을 카멜케이스(camelCase)로 작성하세요! {/3-camelcase-salls-most-of-the-things/}

앞서 말씀드렸듯 JSX는 자바스크립트로 변환되고, JSX 안에 작성된 속성(attributes)들은 자바스크립트 객체의 '키(keys)'가 됩니다. 여러분이 직접 만든 컴포넌트에서는 이런 속성들을 변수에 담아 읽어오고 싶을 때가 많을 거예요. 하지만 자바스크립트에는 변수 이름을 지을 때 지켜야 할 제약사항이 있습니다. 예를 들면, 변수명에 대시(-)를 포함할 수 없고, class와 같은 예약어(reserved words)를 사용할 수도 없죠.

이것이 바로 리액트에서 많은 HTML 속성과 SVG 속성들을 카멜케이스(camelCase)로 작성하는 이유랍니다. 예를 들어, HTML에서의 stroke-width 대신 JSX에서는 strokeWidth를 사용해야 합니다. 또한 class는 자바스크립트의 예약어이기 때문에, 리액트에서는 해당하는 DOM 속성의 이름을 따서 className이라고 작성합니다:

<img 
  src="[https://i.imgur.com/yXOvdOSs.jpg](https://i.imgur.com/yXOvdOSs.jpg)" 
  alt="Hedy Lamarr" 
  className="photo"
/>

이러한 속성들은 DOM 컴포넌트 props 목록에서 모두 확인하실 수 있습니다. 혹시라도 실수로 잘못 적었더라도 너무 걱정하지 마세요. 리액트가 브라우저 콘솔에 올바른 수정 방법을 안내하는 친절한 메시지를 띄워줄 테니까요!

⚠️ 주의사항:
역사적인 이유로 인해, 웹 접근성을 위한 aria-* 속성과 커스텀 데이터 속성인 data-* 속성만큼은 예외적으로 HTML에서 쓰던 방식 그대로 대시(-)를 포함하여 작성합니다.

꿀팁(Pro-tip): JSX 변환기 사용하기 {/pro-tip-use-a-jsx-converter/}

기존 HTML의 방대한 마크업 속성들을 전부 일일이 카멜케이스로 바꾸고 닫는 태그를 붙이는 건 상당히 지루한 작업이 될 수 있어요! 그래서 기존 HTML과 SVG 코드를 JSX로 번역해 주는 변환기(converter)를 사용하는 것을 추천해 드립니다. 이런 변환기들은 실무에서 무척 유용하게 쓰이지만, 여러분 스스로 편하게 JSX를 작성할 수 있도록 내부에서 어떤 일들이 일어나고 있는지 규칙을 이해해 두는 것은 여전히 매우 중요하답니다.

모든 규칙을 적용하여 수정한 최종 코드는 이렇습니다:

export default function TodoList() {
  return (
    <>
      <h1>Hedy Lamarr's Todos</h1>
      <img 
        src="[https://i.imgur.com/yXOvdOSs.jpg](https://i.imgur.com/yXOvdOSs.jpg)" 
        alt="Hedy Lamarr" 
        className="photo" 
      />
      <ul>
        <li>Invent new traffic lights</li>
        <li>Rehearse a movie scene</li>
        <li>Improve the spectrum technology</li>
      </ul>
    </>
  );
}
img { height: 90px }

👨‍🏫 핵심 요약:
자, 이제 JSX가 왜 존재하고 컴포넌트 안에서 어떻게 사용하는지 감이 오시죠?

  • 리액트 컴포넌트는 서로 연관되어 있다는 이유로 렌더링 로직과 마크업을 한곳에 그룹화하여 보관합니다.
  • JSX는 HTML과 비슷하지만 몇 가지 구문상의 차이가 있습니다. 필요하다면 언제든 변환기를 마음껏 활용해 보세요.
  • 개발 중 마주치는 에러 메시지는 마크업을 올바르게 수정할 수 있도록 도와주는 좋은 길잡이가 될 것입니다.

일부 HTML을 JSX로 변환해 보세요 {/convert-some-html-to-jsx/}

아래 HTML 코드가 컴포넌트 안으로 복사되었지만, 현재 유효한 JSX가 아닙니다. 직접 올바른 JSX로 고쳐보세요!

export default function Bio() {
  return (
    <div class="intro">
      <h1>Welcome to my website!</h1>
    </div>
    <p class="summary">
      You can find my thoughts here.
      <br><br>
      <b>And <i>pictures</b></i> of scientists!
    </p>
  );
}
.intro {
  background-image: linear-gradient(to left, violet, indigo, blue, green, yellow, orange, red);
  background-clip: text;
  color: transparent;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.summary {
  padding: 20px;
  border: 10px solid gold;
}

직접 손으로 하나하나 규칙을 적용하며 고칠지, 변환기를 사용할지는 여러분의 선택입니다!

정답 확인하기
export default function Bio() {
  return (
    <div>
      <div className="intro">
        <h1>Welcome to my website!</h1>
      </div>
      <p className="summary">
        You can find my thoughts here.
        <br /><br />
        <b>And <i>pictures</i></b> of scientists!
      </p>
    </div>
  );
}
.intro {
  background-image: linear-gradient(to left, violet, indigo, blue, green, yellow, orange, red);
  background-clip: text;
  color: transparent;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.summary {
  padding: 20px;
  border: 10px solid gold;
}

👨‍🏫 강사의 해설:
1. return 안의 최상위 엘리먼트가 두 개(divp)이므로, 빈 태그 <> 나 새로운 부모 <div>로 전체를 감싸주어야 합니다.
2. class 속성은 자바스크립트 예약어이므로 모두 className으로 수정해야 합니다.
3. <br> 태그는 닫는 태그가 명시되지 않은 형태이므로 <br /> 형태로 올바르게 닫아주어야 합니다.
4. <b>And <i>pictures</b></i> 부분이 잘못 교차되어 있습니다. 태그가 열린 순서의 역순으로 닫혀야 올바른 구조가 되므로 <b>And <i>pictures</i></b>로 수정해야 합니다!


사이트맵 (Sitemap)

모든 문서 페이지 개요 확인하기

profile
프론트에_가까운_풀스택_개발자

0개의 댓글