JSX는 자바스크립트 파일 안에 HTML과 유사한 마크업을 작성할 수 있게 해주는 자바스크립트 구문 확장(syntax extension)이에요. 컴포넌트를 작성하는 다른 방법들도 있지만, 대부분의 리액트 개발자들은 JSX 특유의 간결함을 선호하며, 실제로 대부분의 코드베이스에서 JSX를 채택하여 사용하고 있답니다.
이 장에서 배울 내용:
웹은 본래 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 코드를 가지고 있다고 가정해 볼까요?
<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보다 조금 더 엄격하고, 지켜야 할 몇 가지 규칙이 더 있기 때문이에요! 화면에 나타난 에러 메시지를 읽어보면 마크업을 어떻게 고쳐야 할지 안내해 줄 겁니다. 아니면 아래의 가이드를 차근차근 따라오셔도 좋습니다.
대부분의 경우 리액트 화면에 나타나는 에러 메시지가 문제의 원인을 찾는 데 큰 도움을 줍니다. 막히는 부분이 있다면 꼭 에러 메시지를 천천히 읽어보세요!
컴포넌트에서 여러 개의 엘리먼트를 반환하고 싶다면, 반드시 그것들을 하나의 부모 태그로 감싸주어야 합니다.
예를 들어, 가장 흔하게는 <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는 겉보기엔 HTML처럼 생겼지만, 그 내부 동작(under the hood)을 살펴보면 평범한 자바스크립트 객체(plain JavaScript objects)로 변환됩니다. 자바스크립트 문법을 생각해 볼까요? 하나의 함수에서 두 개의 객체를 반환하고 싶을 때 배열로 감싸지 않고서는 불가능하죠? 이와 같은 맥락으로, 두 개의 JSX 태그 역시 다른 부모 태그나 프래그먼트로 감싸주지 않으면 함수가 올바르게 반환할 수 없는 것이랍니다.
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>
</>
👨🏫 강사의 시각 자료:
(위 코드에서 불러오는 이미지는 다음과 같이 화면에 나타나게 됩니다.)
앞서 말씀드렸듯 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에서 쓰던 방식 그대로 대시(-)를 포함하여 작성합니다.
기존 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가 아닙니다. 직접 올바른 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안의 최상위 엘리먼트가 두 개(div와p)이므로, 빈 태그<>나 새로운 부모<div>로 전체를 감싸주어야 합니다.
2.class속성은 자바스크립트 예약어이므로 모두className으로 수정해야 합니다.
3.<br>태그는 닫는 태그가 명시되지 않은 형태이므로<br />형태로 올바르게 닫아주어야 합니다.
4.<b>And <i>pictures</b></i>부분이 잘못 교차되어 있습니다. 태그가 열린 순서의 역순으로 닫혀야 올바른 구조가 되므로<b>And <i>pictures</i></b>로 수정해야 합니다!