All'alba vincerò

At dawn, I will win!

카테고리 없음

[React] JSX: JavaScript에서 HTML 마크업 사용

나디아 Nadia 2024. 7. 30. 17:52

📌 JSX

(JavaScript XML)

JavaScript 파일에서 HTML과 비슷한 마크업을 작성할 수 있는 함수 

  • JavaScript를 확장한 문법
  • JSX는 함수⭕ (HTML ❌❌❌)
  • 웹 표준이 아니다 ❌ (브라우저에서 해석 불가)

 

 

템플릿 리터럴을 JSX 대신 사용하지 않는 이유

  • 템플릿 리터럴은 긴 DSL을 내장하는 데 잘 작동하지만, 내장된 ECMAScript 표현식과 식별자 범위를 사용할 때 문법적으로 혼잡하다.

 


📌 JSX 문법 

 

 

✅ 멀티 라인(Multi-line)

 

⏩ JSX 구문은 괄호() 안에 감싸서 사용한다. 

function WhatIsJSX() {
  return (
    <main>
       <h1>JSX란?</h1>
       <p>
         JSX를 사용하면 React Components 내부에서 HTML처럼 보이는 구문을 작성할 수 있습니다.
       </p>
    </main>
 );
}

export default WhatIsJSX;

 

 

 

⏩ 단 하나의 최상위 요소만 반환한다. 

  • 형제 요소를 감싸야 할 때에는 React.Fragment 컴포넌트 약식인 <>...</> 를 사용한다.
function Authors() {
  return (
    <>
      <h2>작성자</h2>
        <ul>
          <li>A</li>
          <li>B</li>
          <li>C</li>
        </ul>
    </>
  );
}

export default Authors;

 

 

 

 

✅ 셀프 클로징 태그(Self-Closing Tags)

 

⏩ 스스로 닫아야 하는 요소(<input />, <img />, <br /> 등)의 경우, 명시적으로 닫아야 정상적으로 작동한다. 

function Form() {
  return (
    <form>
       <img src="search.png" alt="돋보기" />
       <br />
       <input name="term" type="text" />
    </form>
  )
}

 

 

 

 

✅ 클래스 이름(className) 속성

 

⏩ JSX 구문에서 클래스를 설정하려면 className 속성을 사용해야 한다. 

function Avatar() {
  return (
    <img
      src="avatar.png"
      className="avatar"
      alt="아바타"
    />
  )
}

 

 

 

 

✅ 속성명은 카멜 케이스(camelCase)

 

⏩ JSX 구문에서 태그 속성을 사용할 땐 카멜 케이스를 사용한다.

  • 하이픈(-)이 포함된 속성 이름은 사용 ❌
function Icon() {
  return (
    <svg>
      <rect
        strokeWidth="5"
      />
    </svg>
  )
}

 

 

 

 

✅ null 반환 (표시 X)

 

데이터가 로딩 중일 때, React 컴포넌트에 아무것도 표시하지 않으려면 null을 반환한다. 

  • 해당 컴포넌트가 로딩 중에는 아무 UI도 렌더링하지 않음 👉 깔끔한 UI 유지
function WhatIsJSX() {

  if (isLoading() === true) {
    return null;
  }

  return (
    <main>
       <h1>JSX란?</h1>
       <p>
         JSX를 사용하면 React Components 내부에서 HTML처럼 보이는 구문을 작성할 수 있습니다.
       </p>
    </main>
 );
}

export default WhatIsJSX;

 

 

 

 

 

✅ 데이터 바인딩

 

➡️ 부모 컴포넌트(상위)에서 👉 자식 컴포넌트(하위)에 데이터를 전달할 수 있는데,
이렇게 전달하는 데이터를 props라고 부른다. 

 

 

 

🪄 부모 컴포넌트(상위)에서 자식 컴포넌트(하위)에 데이터를 전달하는 방법

 

1. import로 데이터 가져오기 

import * as learnData from '../data/learn.js'

 

 

2. 구조 분해 할당으로 저장

  const { statusMessages } = learnData;

 

 

3. 저장한 데이터를 return에서 props로 전달

  return (
    <dl className="descriptionList">
      <DataBinding statusMessages={statusMessages} />
    </dl>
  );

 

 

4. 하위 컴포넌트에서 전달된 값을 props로 받아온다.

 

 

 

* 데이터 랜덤으로 바인딩하기

jsx-markup.jsx

import DataBinding from './data-binding';
import * as learnData from '../data/learn.js'

function JSX_Markup() {

  const { statusMessages } = learnData;
  
  return (
    <dl>
      <DataBinding statusMessages={statusMessages} />
    </dl>
  );
}

export default JSX_Markup;

 

 

data-binding.jsx

import { randomNumber } from './../utils/randomNumber';

function DataBinding({ statusMessages }) {
  const statusMessage = statusMessages[randomNumber(0, statusMessages.length - 1)];

  return (
    <>
      <dt>데이터 바인딩(data binding)</dt>
      <dd>
        <p>상태 메시지(status message)를 연결해 화면에 출력합니다.</p>
        
        <span className="status">
          {statusMessage}
        </span>
        
      </dd>
    </>
  );
}

export default DataBinding;

 

 

randomNumber.js

export function randomNumber(min = 0, max = 10) {
  if (min > max) throw new Error('max 보다 min 값이 큽니다.');
  
  return Math.round(Math.random() * (max - min) + min);
}

 

 

 

 

✅ 조건부 렌더링(conditional rendering)

 

 

* 조건부로 이미지 렌더링하기

if문

function ConditionalRendering({ imageType }) {

  let imagePath = '';
  let printText = '';

  if (imageType.toLowerCase().includes('react')) {
    imagePath = reactImagePath;
    printText = 'React';
  }

  if (imageType.toLowerCase().includes('vite')) {
    imagePath = viteImagePath;
    printText = 'Vite';
  }

 


switch문

function ConditionalRendering({ imageType }) {

  let imagePath = '';
  let printText = '';

  switch (imageType.toLowerCase()) {
    case 'react':
      imagePath = reactImagePath;
      printText = 'React';
      break;
    case 'vite':
      imagePath = viteImagePath;
      printText = 'Vite';
      break;
    case 'next.js':
      imagePath = nextJsImagePath;
      printText = 'Next.js';
      break;
    case 'kakao talk':
      imagePath = kakaoTalkImagePath;
      printText = 'Kakao Talk';
      break;
    default:
      printText = '허용된 이미지 타입이 존재하지 않습니다.';
  }

 

 

3항 연산자

function ConditionalRendering({ imageType }) {

  const spinnerOrVite =
    randomNumber(0, 1) > 0.5 ? (
      <img className="spinner" src="/icons/spinner.svg" alt="로딩 중..." />
    ) : (
      <img src="/vite.svg" alt="Vite" style={{ height: 42 }} />
    );

 

 

import { randomNumber, typeOf } from '../utils';

import reactImagePath from '../assets/react.svg?url';
import viteImagePath from '../assets/vite.svg?url';


function ConditionalRendering({ imageType }) {

  let imagePath = '';
  let printText = '';

  if (imageType.toLowerCase().includes('react')) {
    imagePath = reactImagePath;
    printText = 'React';
  }

  if (imageType.toLowerCase().includes('vite')) {
    imagePath = viteImagePath;
    printText = 'Vite';
  }


  const spinnerOrVite =
    randomNumber(0, 1) > 0.5 ? (
      <img className="spinner" src="/icons/spinner.svg" alt="로딩 중..." />
    ) : (
      <img src="/vite.svg" alt="Vite" style={{ height: 42 }} />
    );


  return (
    <>
      <dt>조건부 렌더링(conditional rendering)</dt>
      <dd>
        <p>이미지 타입(image type)에 따라 렌더링 여부를 결정합니다.</p>
        <div className="conditionalRendering">

          <img src={imagePath} alt="" />
          <p>{printText}</p>
          
        </div>
      </dd>
      
      <dd style={{ marginTop: 12 }}>
        <p>spinner 또는 vite 이미지가 랜덤으로 화면에 렌더링 되도록 합니다.</p>
        
        <div className="conditionalRendering">
          {spinnerOrVite}
        </div>
        
      </dd>
    </>
  );
}

export default ConditionalRendering;

 

 

 

 

✅ 조건부 표시

  • 실제 렌더링은 되었으나 CSS로 화면에서 숨김

  • 조건부 렌더링(Conditional Rendering) 
    👉 SSR: 서버에서 html 페이지 생성, 마크업에 포함/불포함 여부에 따라 진행
  • 조건부 표시(Conditional Display) 
    👉 CSR: 전적으로 CSS에 의존

 

 

 

1. class 속성 사용

function ConditionalDisplay({ isShowImage }) {

  const pictureClassNames = `Picture ${isShowImage ? '' : 'hidden'}`.trim();

  return (
    <>
      <dt>조건부 표시(conditional display)</dt>
      <dd>
        <p>
          표시(display) 여부에 따라 이미지가 화면에서 감춰지거나 표시됩니다.
        </p>
        
        {/* class 속성 사용 */}
        <picture
          className={pictureClassNames}
        >
        
          <source type="image/avif" srcSet="/react/react.avif" />
          <source type="image/webp" srcSet="/react/react.webp" />
          <img src="/react/react.png" alt="React" height={42} />
        </picture>
      </dd>
    </>
  );
}

export default ConditionalDisplay;
  • 이미지를 표시하지 않을 때는 className에 빈 문자('') 사용
    - null, undefined 사용 X
    ➡️ {isShowImage ? '' : 'hidden'}의 반환값이 문자열 내부로 들어가서 문자형으로 암시적 형변환이 일어나면 빈문자가 아니라 'undefined'가 되기 때문
Picture ${isShowImage ? '' : 'hidden'}

 

 

 

2. style 속성 사용

  • style 설정은 객체{}로 사용해야 함!!!
  • 권장 X
function ConditionalDisplay({ isShowImage }) {

  const pictureStyles = {
    display: isShowImage ? 'inline-block' : 'none',
    fontSize: 42,
    lineHeight: 2.4,
    letterSpacing: '2px',
  };

  return (
    <>
      <dt>조건부 표시(conditional display)</dt>
      <dd>
        <p>
          표시(display) 여부에 따라 이미지가 화면에서 감춰지거나 표시됩니다.
        </p>
        
        {/* style 속성 사용 */}
        <picture
          style={pictureStyles}
        >
        
          <source type="image/avif" srcSet="/react/react.avif" />
          <source type="image/webp" srcSet="/react/react.webp" />
          <img src="/react/react.png" alt="React" height={42} />
        </picture>
      </dd>
    </>
  );
}

export default ConditionalDisplay;

 

 

 

3. html 요소의 hidden 속성 사용

function ConditionalDisplay({ isShowImage }) {

  return (
    <>
      <dt>조건부 표시(conditional display)</dt>
      <dd>
        <p>
          표시(display) 여부에 따라 이미지가 화면에서 감춰지거나 표시됩니다.
        </p>
        
        {/* html 요소의 hidden 속성 사용 */}
        <picture
          hidden={!isShowImage}
        >
        
          <source type="image/avif" srcSet="/react/react.avif" />
          <source type="image/webp" srcSet="/react/react.webp" />
          <img src="/react/react.png" alt="React" height={42} />
        </picture>
      </dd>
    </>
  );
}

export default ConditionalDisplay;

 

 

 

 

✅ 리스트(list) 렌더링

 

  • React에서 리스트를 렌더링 할 경우, key 속성을 설정해야 한다.

 

🪄 key 속성

리스트를 만들 때 포함해야 하는 특수한 문자열 어트리뷰트

  • React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕는다.
    👉 비교 알고리즘으로 빠르게 비교하기 위해
  • key는 고유하게 식별할 수 있는 문자열을 사용하는 것이 좋다.
    - index는 최후의 수단으로 사용
  • 배열 안에서 형제 사이에서 고유해야 하고, 전체 범위에서 고유할 필요는 없다.
    (두 개의 다른 배열을 만들 때 동일한 key를 사용할 수 있다)

function ListItem(props) {
  // 여기에는 key를 지정할 필요가 없다.
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  
  const listItems = numbers.map((number) =>
    //배열 안에 key를 지정해야 한다.
    <ListItem key={number.toString()} value={number} />
  );
  
  return (
    <ul>
      {listItems}
    </ul>
  );
}

 

 

 

리스트 렌더링 예제

const tweets = [
  {
    id: 1,
    stars: 13,
    text: 'AAA',
  },
  {
    id: 3,
    stars: 51,
    text: "BBB",
  },
  {
    id: 4,
    stars: 19,
    text: "CCC",
  },
];

const stars = tweets.map((tweet) => tweet.stars); // [13, 51, 19]
import tweets from './tweets';

function Tweets() {
  return (
    <ul id="tweets">
      {tweets.map((tweet) => (
         <li key={tweet.id}>{tweet.text}</li>
      ))}
    </ul>
  );
}

export default Tweets;

 

 

 

 

⏩ 배열 데이터 정렬

 

데이터

export const statusMessagesWithID = [
  { id: 'message-xyz', message: '⌛️ 대기' },
  { id: 'message-air', message: '⏳ 로딩 중...' },
  { id: 'message-ckd', message: '✅ 로딩 성공!' },
  { id: 'message-eiu', message: '❌ 로딩 실패.' },
];

 

 

let demoListUsingStatement = [];

for (let item of items) {
   demoListUsingStatement.push(<li key={item.id}>{item.message}</li>);
}

<ul>{demoListUsingStatement}</ul>

 

 

const demoList = items.map((item) => {
   return <li key={item.id}>{item.message}</li>;
});

<ul>{demoList}</ul>

 

 

함수

const renderDemoList = () =>
  items.map(({ id, message }) => {
     return <li key={id}>{message}</li>;
  });

<ul>{renderDemoList()}</ul>

 

 

 

 

역순 정렬

 

직접 삽입

{items.toReversed().map((item) => (
  <li key={item.id}>{item.message}</li>
))}

 

 

함수

const renderList = ({ reverse = false } = {}) => {

   let listItems = items; // 대기 → 로딩 실패 순

   if (reverse) {
     listItems = items.toReversed(); // ES 2023 (v14) 추가
   }

   // 리스트 렌더링 결과 반환
   return listItems.map((item) /* string */ => {
     return <li key={item.id}>{item.message}</li>;
   });
};

<ul className="renderList">{renderList?.()}</ul>

 

 

배열의 순차 정렬, 역순 정렬 예제

function RenderLists({ items /* string[], Array<string> */ }) {

  // JSDOC
  /**@type{() => Array<React.ReactElement>} */
  const renderList = ({ reverse = false } = {}) => {

    let listItems = items; // 대기 → 로딩 실패 순

    if (reverse) {
      listItems = items.toReversed(); // 역순 정렬
    }


    // 리스트 렌더링 결과 반환
    return listItems.map((item) /* string */ => {
      return <li key={item.id}>{item.message}</li>;
    });
  };


  return (
    <>
      <dt>리스트 렌더링(list rendering)</dt>
      <dd>
        <p>상태 메시지(status messages) 배열을 리스트 렌더링합니다.</p>
        <ul className="renderList">{renderList?.()}</ul>
      </dd>

      <dd>
        <p>상태 메시지(status messages) 배열을 역순 정렬하여 렌더링합니다.</p>
        <ul className="renderList">{renderList?.({ reverse: true })}</ul>
      </dd>
    </>
  );
}

 

 

 

 

⏩객체 데이터 정렬

  • object.entries() 사용

 

데이터

export const reactLibrary = {
  name: 'React',
  author: '조던 워케(Jordan Walke)',
  writtenIn: 'JavaScript',
  type: 'JavaScript 라이브러리',
  license: 'MIT',
};

 

 

직접 삽입

import { Fragment } from 'react';

{Object.entries(reactLibrary).map(([ key, value ]) => {
  return (
    <Fragment key={key}>
      <dt>{key}</dt>
      <dd>{value}</dd>
    </Fragment>
  );
})}

 

 

함수

const renderDefinitionList = (objectData) => {
  const definitionItems = Object.entries(objectData).map(([key, value]) => {

    return (
      <Fragment key={key}>
        <dt>{key}</dt>
        <dd>{value}</dd>
      </Fragment>
    );
  });

  return <dl className="reactLibrary">{definitionItems}</dl>;
};

<dd>
  {renderDefinitionList(reactLibrary)}
</dd>

 

 


 

  • JSX Runtime Mode [CLASSIC] 👉 직접 주입
import React from 'react';

JSX => React.createElement(type, props, ...children);

 

 

  • JSX Runtime Mode [AUTOMATIC] 👉 자동 주입
import { jsx } from 'react/jsx-runtime';

JSX => jsx(type, props);

 

 

  • React.Fragment 컴포넌트 사용이 필요할 때👉 직접 주입
    - 직접
    <Fragment> 컴포넌트 사용 ⭕
import { Fragment } from 'react';

 

 


📌 JSX 트랜스폼

: JSX 문법을 JavaScript로 변환하는 과정

 

 

1. 바벨 @bable

  • main 스크립트 파일의 타입을(text/babel or text/jsx) 설정해야 사용 가능
  • module도 사용하기 위해 data-type="module" 추가 설정
<!-- @babel/standalone -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

<!-- main.js 스크립트의 타입을(text/babel or text/jsx) 설정해야 사용 가능 -->
<!-- module도 사용하기 위해 data-type="module" 추가 설정 -->
<script type="text/jsx" data-type="module" src="src/main.js" ></script>

 

 

 

2. 타입 스크립트(TypeScript)

: 타입 시스템을 통해 코드에서 발생할 수 있는 오류 방지

  • .ts : TypeScript만 사용하는 파일 확장자
  • .tsx : JSX를 사용하는 파일 확장자

  • 타입 스크립트 과정
    JSX + TypeScript(개발자가 사용) 👉 브라우저가 이해 ❌

    👉 TypeScript가 트랜스파일링(Transpiling) 👉 React API + JavaScript로 바꿔서 해석

 

 

* 로컬 환경에서 사용

 

설치

pnpm add typescript -D

 

 

사용

pnpm tsc

 

 

파일에 사용

  •  ./src/**/*.tsx : 모든 tsx 파일 변환
pnpm tsc src/**/*.tsx --jsx react --module esnext

 

 

 

버전 확인

pnpm tsc --version

 

 

도움말

pnpm tsc --help

 

 


 

 

JSX 소개 – React

A JavaScript library for building user interfaces

ko.legacy.reactjs.org

 

 

리스트와 Key – React

A JavaScript library for building user interfaces

ko.legacy.reactjs.org