본문 바로가기

JSX

1. JSX란?

JSX(JavaScript XML)는 React에서 트리 구조를 쉽게 표현하기위해 메타(구 페이스북)에서 임의로 만든 새로운 문법입니다. JSX를 통해 JavaScript 코드 안에서 HTML과 유사한 마크업을 작성할 수 있습니다. JavaScript의 표준 구문이 아니기 때문에 그대로 실행하면 에러가 발생하며 컴파일 시에 일반 JavaScript 코드로 변환됩니다.

JSX를 사용하면 JavaScript 코드와 HTML 태그를 함께 작성할 수 있어 코드의 가독성과 유지보수성이 높아집니다. JSX가 없다면 React 엘리먼트를 만들기 위해 createElement 함수를 여러 번 호출해야 하는 번거로움이 있습니다.

다음은 src에 있는 App.js 파일에 작성된 JSX 구문과 이것이 실제로 변환되는 JavaScript 코드입니다.

// JSX 구문
function App() {
  return (
    <div className="App">
      <h2>hello world</h2>
    </div>
  );
}

// 변환된 JavaScript 코드
function App() {
  return React.createElement(
    'div',
    { className: 'App' },
    React.createElement('h2', null, 'hello world'),
  );
}

export default App;

위 예시를 통해 JSX를 사용하면 복잡한 createElement 호출 대신 익숙한 HTML과 유사한 문법으로 화면 구조를 작성할 수 있음을 알 수 있습니다.

2. JSX 문법

2.1 최상위 요소 규칙

JSX에서는 여러 요소를 반환할 때 반드시 하나의 부모 요소로 감싸야 합니다. 이는 React가 컴포넌트의 변화를 감지하고 Virtual DOM을 업데이트할 때 단일 컴포넌트로 관리하는 것이 효율적이기 때문입니다.

// 잘못된 작성 방법
function App() {
  return (
    <h1>제목</h1>
    <p>내용</p>    // SyntaxError 발생
  );
}

// 옳은 작성 방법
function App() {
  return (
    <div>
      <h1>제목</h1>
      <p>내용</p>
    </div>
  );
}

export default App;

만약 추가적인 div를 피하고 싶다면, React Fragment를 사용할 수 있습니다. Fragment(<></>)는 DOM에 별도의 노드를 추가하지 않고 여러 자식 요소를 그룹화할 수 있는 기능입니다.

function App() {
  return (
    <>
      <h1>제목</h1>
      <p>내용</p>
    </>
  );
}

export default App;

2.2 스타일링 방법

JSX에서는 세 가지 주요 스타일링 방법을 사용할 수 있습니다.

2.2.1 인라인 스타일

JSX에서 인라인 스타일은 문자열 형태가 아닌 객체 형태로 작성합니다. CSS 속성 이름은 JavaScript 객체의 속성으로 변환되므로, - 문자를 포함하는 속성들은 camelCase로 작성해야 합니다. 예를 들어 background-color는 backgroundColor로 작성합니다.

function App() {
  // 스타일 객체로 정의
  const style = {
    color: 'red',
    marginBottom: '24px',
    fontSize: '16px',
    backgroundColor: 'white',
  };

  return (
    <div>
      {/* 미리 정의한 스타일 객체 사용 */}
      <h1 style={style}>제목</h1>

      {/* 인라인으로 직접 스타일 객체 작성 */}
      <p
        style={{
          backgroundColor: 'blue',
          fontSize: '20px',
          padding: '10px',
        }}
      >
        내용
      </p>
    </div>
  );
}

export default App;

camelCase는 프로그래밍에서 자주 사용되는 명명 규칙(naming convention) 중 하나입니다. 여러 단어를 조합할 때 두 번째 단어부터는 첫 글자를 대문자로 표기하고, 첫 단어는 소문자로 시작합니다. 단어 사이에 공백이나 특수 문자는 사용하지 않습니다. camelCase라는 이름은 단어들이 연결된 모양이 낙타(camel)의 등처럼 보인다고 해서 붙여졌습니다.

2.2.2 className을 사용한 외부 CSS

JSX에서는 HTML의 class 속성 대신 className을 사용합니다. 외부 CSS 파일을 사용하면 스타일을 컴포넌트와 분리하여 관리할 수 있고, 재사용성도 높일 수 있습니다.

/* App.css */
.button {
  background-color: blue;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

/* 여러 클래스를 함께 사용할 수 있습니다 */
.button.large {
  font-size: 1.2rem;
  padding: 15px 30px;
}
// App.jsx
import './App.css';

function App() {
  return (
    <div>
      {/* 기본 버튼 */}
      <button className="button">클릭하세요</button>

      {/* 여러 클래스 적용 */}
      <button className="button large">큰 버튼</button>
    </div>
  );
}

export default App;

JSX에서 제공하는 className을 사용하지 않고 우리가 사용했던 class를 그대로 사용하면 어떻게 될까요? react v.16 이상부터는 아래와 같이 실행은 되지만 console창에 warning이 뜨는 것을 볼 수 있습니다. JSX를 사용하는 만큼 권고사항에 따라주세요.

더 자세한 스타일 적용 방법은 4장 중급개념에서 알아보겠습니다.

2.3 태그 닫기 규칙

React에서는 모든 태그를 반드시 닫아야 합니다. HTML에서 단일 태그로 사용되는 요소들(<input>, <img>, <br> 등)도 React에서는 반드시 닫는 형식으로 작성해야 합니다. 태그를 닫는 방법에는 두 가지가 있습니다.

  1. 여는 태그와 닫는 태그 쌍으로 작성 <input></input>
  2. self-closing 태그 사용 <input />
// 잘못된 예
function App() {
  return (
    <form>
      <label>이름</label>
      <input>            {/* 에러 발생 */}
      <br>              {/* 에러 발생 */}
      <img src="이미지"> {/* 에러 발생 */}
    </form>
  );
}

function App() {
  return (
    <form>
      <label>이름</label>
      <input></input>
      <br />   {/* self-closing */}
      <img src="이미지" />   {/* self-closing */}
    </form>
  );
}

export default App;

2.4 주석

JSX에서 주석은 작성하는 위치에 따라 다른 방식을 사용합니다. JavaScript 영역에서는 일반적인 JavaScript 주석을 사용하고, JSX 내부에서는 중괄호로 감싼 특별한 형식을 사용합니다.

function App() {
  // 여기는 JavaScript 영역이므로 일반적인 주석 사용
  /* 
    JavaScript 영역의
    여러 줄 주석
  */

  return (
    <div>
      {/* JSX 내부의 한 줄 주석 */}
      <h1>안녕하세요</h1>

      {/* 
        JSX 내부의
        여러 줄 주석 
      */}
      <p>리액트 컴포넌트입니다.</p>

      <div
        // 이런 주석은 동작하지 않습니다
        className="container" // 속성 사이에서는 이렇게 주석을 작성할 수 있습니다
      >
        내용
      </div>
    </div>
  );
}

주의할 점은 JSX 내부에서 // 형식의 한 줄 주석은 사용할 수 없습니다. 반드시 {/* */} 형식을 사용해야 합니다. 단, 태그의 속성을 작성하는 줄에서는 일반적인 JavaScript 주석을 사용할 수 있습니다.

2.5 JavaScript 사용하기

JSX 내부에서 자바스크립트를 사용하려면 중괄호 {}를 사용합니다. 중괄호 안에는 모든 유효한 자바스크립트 표현식을 넣을 수 있습니다.

function App() {
  const name = '라이캣';
  const number = 5;

  return (
    <div>
      {/* 변수 사용 */}
      <p>안녕하세요, {name}!</p>

      {/* 간단한 계산식 */}
      <p>5 + 5 = {5 + 5}</p>

      {/* 변수를 사용한 계산식 */}
      <p>
        {number} + 5 = {number + 5}
      </p>

      {/* 문자열 결합 */}
      <p>{'Hello' + ' World'}</p>

      {/* 함수 호출 */}
      <p>{console.log('안녕하세요')}</p>
    </div>
  );
}

2.5.1 조건부 렌더링

조건부 렌더링이란 특정 조건에 따라 다른 결과를 보여주는 것을 말합니다. React에서는 if문을 직접 사용할 수 없기 때문에 삼항 연산자나 논리 연산자를 활용합니다.

function App() {
  const isLogin = true;
  const num = 3;
  const message = null;

  return (
    <div>
      {/* 삼항 연산자를 이용한 조건부 렌더링 */}
      {isLogin ? <p>환영합니다!</p> : <p>로그인이 필요합니다.</p>}

      {/* &&를 이용한 조건부 렌더링 */}
      {isLogin && <p>로그인 상태입니다.</p>}

      {/* 여러 조건을 활용한 예시 */}
      {num > 5 ? (
        <p>5보다 큽니다.</p>
      ) : num < 5 ? (
        <p>5보다 작습니다.</p>
      ) : (
        <p>5입니다.</p>
      )}

      {/* null을 사용한 조건부 렌더링 */}
      {message ? <p>메시지: {message}</p> : null}

      {/* 위 코드는 아래처럼 &&를 사용해 더 간단히 표현할 수 있습니다 */}
      {message && <p>메시지: {message}</p>}
    </div>
  );
}

2.5.2 리스트 렌더링

배열의 각 항목을 렌더링할 때는 주로 map() 메서드를 사용합니다. 이때 각 항목에는 고유한 key 속성을 지정해야 합니다.

function App() {
  const list = [
    { no: 1, area: '대전', visited: false },
    { no: 2, area: '부산', visited: true },
    { no: 3, area: '목포', visited: false },
    { no: 4, area: '제주도', visited: false },
  ];

  return (
    <div>
      <h2>여행 버킷리스트</h2>
      <ul>
        {list.map((item) => (
          <li
            key={item.no}
            style={{
              textDecoration: item.visited ? 'line-through' : 'none',
              color: item.visited ? '#888' : '#000',
            }}
          >
            {item.area}
          </li>
        ))}
      </ul>
    </div>
  );
}

위 예시에서는 visited 속성이 true인 도시에 취소선이 추가됩니다. 이처럼 배열의 데이터를 기반으로 동적으로 UI를 생성할 수 있으며, 각 항목의 속성에 따라 다른 스타일을 적용할 수 있습니다.

React는 key를 사용하여 리스트에 변화가 있을 때(추가, 제거, 순서 변경 등) 어떤 항목이 변경되었는지 빠르게 파악합니다. 이를 통해 필요한 부분만 효율적으로 업데이트할 수 있습니다. 예를 들어 위 코드에서는 각 도시의 고유 번호인 no를 key로 사용하고 있습니다.

list.map((item) => (
  <li
    key={item.no} // 각 도시의 고유 번호를 key로 사용
  >
    {item.area}
  </li>
));

만약 key를 제공하지 않으면 React는 전체 리스트를 불필요하게 다시 렌더링할 수 있어 성능이 저하될 수 있습니다. 따라서 배열을 렌더링할 때는 반드시 각 항목에 고유한 key를 지정해주어야 합니다.

key props

배열의 요소들을 렌더링할 때는 각 요소마다 고유한 key 값을 지정해야 하는데 이것을 key props라고 합니다. key props을 사용하면 리스트에 변화(추가, 제거, 순서 변경 등)가 발생했을 때 React가 어떤 항목이 변경되었는지 빠르게 감지하고, 렌더링을 최적화할 수 있습니다. key props을 제공하지 않으면 React가 불필요한 렌더링을 할 수 있어 성능이 저하될 수 있습니다.

key 값은 일반적으로 배열의 id 값을 넣어주는데 만약 고유의 값을 찾을 수 없다면 인덱스를 key로 사용하면 되지만 단순히 에러를 제거하기 위한 미봉책일 뿐 권장하지 않습니다.

❗️ key 값은 동일한 컴포넌트 리스트에서만 ‘유일값’이면 됩니다.

참고 | 리스트와 key

3. [실습] 시계 만들기

아래 기본 코드를 사용해 사용해 시계 UI를 만들어 보세요.

  1. 각 시간은 Date 객체의 값을 출력해주세요.
  2. 새로고침할 때마다 현재 시간을 표현합니다.
  3. 스타일도 자유롭게 적용해보세요.
function App() {
  return (
    <div>
      <div>
        <h2>안녕, {name}</h2>
        <div className="newClass" />
      </div>
      <div>
        <h2>년 : {year}</h2>
        <h2>
          월/일 : {month}/{date}
        </h2>
        <h2>
          시간 : {hour}시 {min}분 {sec}초
        </h2>
      </div>
    </div>
  );
}

export default App;