📌 JSX
(JavaScript XML)
: JavaScript 파일에서 HTML과 비슷한 마크업을 작성할 수 있는 함수
- JavaScript를 확장한 문법
- JSX는 함수⭕ (HTML ❌❌❌)
- 웹 표준이 아니다 ❌ (브라우저에서 해석 불가)
- 템플릿 리터럴은 긴 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