React JSX에서 조건부 화면과 배열 목록을 어떻게 표시하나요?
JSX는 React 화면 구조 안에 JavaScript 표현식을 쓰는 문법입니다. 조건문으로 빈 상태를 나누고 배열의 map으로 목록을 표시합니다.
적용 조건·범위: JSX 중괄호에는 문(statement)이 아니라 표현식(expression)을 넣어야 합니다. 조건이 복잡하면 JSX 바깥에서 먼저 계산합니다.
독립 실습: 조건·목록·빈 배열을 한 화면에서 비교하기
JSX 목록과 완료 여부에 따른 문구를 확인하세요.
예제 크게 보기 (새 탭)JavaScript 함수·배열과 TypeScript 객체 타입을 알고 시작합니다. 실행 환경은 React Vite 환경 준비의 React + TypeScript 프로젝트를 사용합니다. 이전 TaskFlow 코드는 필요 없습니다. 생성된 src/main.tsx는 유지하고 별도 연습 프로젝트의 src/App.tsx를 아래 전체 코드로 교체한 뒤 npm run dev로 여는 주소에서 확인합니다.
src/App.tsx 전체 코드
type Task = { id: string; title: string; completed: boolean };
export default function App() {
const tasks: Task[] = [
{ id: 'a', title: '독서', completed: true },
{ id: 'b', title: '산책', completed: false },
];
if (tasks.length === 0) return <p>등록된 작업이 없습니다.</p>;
return (
<ul>
{tasks.map(task => (
<li key={task.id}>
{task.title}: {task.completed ? '완료' : '진행 중'}
</li>
))}
</ul>
);
}
직접 확인: 독서: 완료, 산책: 진행 중 두 항목이 표시됩니다. tasks를 []로 교체하면 빈 상태만 나옵니다.
변형 과제: map 콜백을 중괄호 형태로 바꿔 보고 return이 필요한 이유를 설명합니다.
JSX란
JSX는 JavaScript 안에서 UI 구조를 표현하기 위한 문법입니다.
function Greeting() {
const name = '해비';
return <h1>안녕하세요, {name}님</h1>;
}
중괄호 안에는 값을 계산하는 JavaScript 표현식을 넣을 수 있습니다.
JSX 기본 규칙
하나의 부모 요소를 반환합니다
function Example() {
return (
<section>
<h2>제목</h2>
<p>내용</p>
</section>
);
}
여러 형제 요소를 감싸기 위해 Fragment를 사용할 수도 있습니다.
function Example() {
return (
<>
<h2>제목</h2>
<p>내용</p>
</>
);
}
class 대신 className을 사용합니다
<div className="card">내용</div>
JavaScript 표현식은 중괄호 안에 넣습니다
const completed = true;
return <span>{completed ? '완료' : '진행 중'}</span>;
조건부 렌더링
조건에 따라 다른 UI를 반환할 수 있습니다.
function Status({ completed }: { completed: boolean }) {
return <span>{completed ? '완료' : '진행 중'}</span>;
}
조건이 참일 때만 보여주고 싶다면 &&도 사용할 수 있습니다.
{completed && <strong>완료된 작업입니다.</strong>}
조건이 복잡해지면 JSX 안에 긴 삼항 연산자를 넣기보다 렌더링 전에 값을 계산하거나 작은 컴포넌트로 분리하는 편이 읽기 쉽습니다.
map으로 목록 렌더링
배열을 화면 요소로 바꿀 때 map을 자주 사용합니다.
const tasks = [
{ id: 'task-1', title: 'JSX 공부', completed: false },
{ id: 'task-2', title: 'TaskFlow 만들기', completed: true },
];
return (
<ul>
{tasks.map((task) => (
<li key={task.id}>
{task.title}
</li>
))}
</ul>
);
key의 자세한 기준은 별도 글에서 다룹니다. 지금은 같은 목록 안에서 각 항목을 구분할 수 있는 안정적인 id가 필요하다는 정도만 기억하면 됩니다.
빈 목록 처리
배열이 비어 있을 때 빈 ul만 보여주기보다 사용자에게 상태를 알려주는 편이 좋습니다.
if (tasks.length === 0) {
return <p>등록된 작업이 없습니다.</p>;
}
자주 발생하는 오류와 해결
map 안에서 아무것도 반환되지 않습니다
중괄호를 사용했다면 return이 필요합니다.
tasks.map((task) => {
return <li key={task.id}>{task.title}</li>;
});
괄호를 사용하면 바로 반환할 수 있습니다.
tasks.map((task) => (
<li key={task.id}>{task.title}</li>
));
if 문을 JSX 중괄호 안에 직접 넣었습니다
JSX 중괄호에는 문(statement)이 아니라 표현식(expression)을 넣어야 합니다. 조건이 복잡하면 JSX 바깥에서 먼저 계산합니다.
학습 완료 체크리스트
- JSX의 기본 규칙을 설명할 수 있습니다.
- 중괄호로 값을 출력할 수 있습니다.
- 조건에 따라 다른 UI를 렌더링할 수 있습니다.
- map으로 배열을 목록으로 바꿀 수 있습니다.
- 빈 배열을 별도 UI로 처리할 수 있습니다.
선택 실습: 할 일 앱에 적용하기
TaskFlow는 작업을 추가·완료·삭제하는 할 일 앱입니다. 이 선택 실습에서는 React로 화면과 상태를 이어서 만듭니다.
Task 배열을 만들고 map으로 실제 목록을 렌더링합니다. 완료된 작업에는 다른 텍스트를 표시하고, 작업이 없으면 빈 상태를 보여줍니다.
선택: 연재 프로젝트와 전체 자료
연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.
TaskFlow 프로젝트 적용
TaskFlow 4단계 실행
시작 상태와 완성 결과를 바꾸어 본문에서 적용한 화면과 동작을 비교하세요.
예제 크게 보기 (새 탭)시작 체크포인트: 이전: React 컴포넌트 개념 정리 UI 재사용 구조 잡기의 완료 상태에서 이어갑니다.
이전 회차 완료 ZIP으로 시작하기. 새 폴더에 풀어 현재 회차 완료본과 나란히 비교합니다.
이전 완료본과 달라진 파일: package.json, src/App.tsx, src/components/TaskItem.tsx, src/components/TaskList.tsx, src/types/task.ts. 아래 변경 설명을 따라 수정하고 전체 코드의 같은 파일과 대조합니다.
변경 순서: src/App.tsx의 예제 배열과 src/components/TaskList.tsx의 map·빈 상태를 연결하고 TaskItem.tsx에서 completed 표시를 분기합니다. 아래 완료 코드와 비교한 뒤 기존 실행 확인 절차를 수행합니다.
독자 질문과 직접 확인
질문: 정적인 작업 목록을 배열로 바꾸면 완료 항목과 빈 목록은 각각 어떻게 표시되는가?
이 글의 step-04-jsx 체크포인트에서 src/App.tsx의 tasks 배열 중 하나의 completed 값을 true로 바꾸고 npm run dev로 화면을 엽니다. 완료 표시가 해당 항목에서만 바뀌는지 봅니다. 이어서 배열을 []로 바꾸어 빈 상태 문구가 보이는지 확인하고 원래 데이터를 복원합니다. key에는 반복 항목의 고유 id를 사용합니다.
예상 화면·상태: 데이터가 두 개이면 목록 2개가 보이고 완료 문구는 completed가 true인 항목에만 나타납니다. 빈 배열이면 작업 카드 대신 빈 상태가 보입니다. 이는 독자가 확인할 기준이며 이번 편집에서 앱을 실행해 관측한 결과가 아닙니다.
항목 수가 맞지 않으면 map에 전달한 배열과 조건문을, 경고가 나오면 각 최상위 반복 요소의 key를 살펴봅니다. npm run build와 실제 브라우저 확인은 이번 편집에서 실행하지 않아 미확인입니다.
다음 학습: 05회 props
이전 체크포인트
step-03-components
현재 상태
Header, TaskList, TaskItem 컴포넌트는 분리됐지만 작업 데이터가 고정되어 있습니다.
이번 목표
Task 배열을 만들고 map으로 실제 목록을 렌더링합니다. 완료된 작업에는 다른 텍스트를 표시하고, 작업이 없으면 빈 상태를 보여줍니다.
Before
TaskItem 안에 고정된 작업 제목
After
Task[] 배열
↓
map
↓
TaskItem 여러 개 렌더링
↓
완료 상태 조건부 표시
실행 확인
npm run dev
npm run build
두 작업이 목록으로 표시되고 각 작업의 완료 상태가 다르게 보이면 됩니다.
완료 체크포인트:
step-04-jsx
전체 코드 뷰어의 모든 파일은 다운로드 ZIP과 같습니다. ZIP은 독립 실행할 수 있는 완료 시점의 코드입니다. Node.js 22.12 이상에서 npm install 후 npm run dev로 실행하고, npm run build로 검사합니다.
전체 파일·코드 보기
실습 프로젝트 전체 코드
index.html
<!doctype html>
<html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>TaskFlow</title></head><body><div id="root"></div><script type="module" src="/src/main.tsx"></script></body></html>
package.json
{
"name": "taskflow-step-04-jsx",
"version": "1.0.0",
"private": true,
"type": "module",
"engines": {
"node": ">=22.12.0"
},
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "tsc --noEmit && vite build",
"preview": "vite preview --host 127.0.0.1"
},
"dependencies": {
"react": "19.3.0",
"react-dom": "19.3.0"
},
"devDependencies": {
"typescript": "7.0.2",
"vite": "8.3.0",
"@vitejs/plugin-react": "6.1.1",
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"@types/node": "26.6.2"
}
}
src/App.tsx
import Header from './components/Header';
import TaskList from './components/TaskList';
import type { Task } from './types/task';
const tasks: Task[] = [
{
id: 'task-1',
title: 'JSX 조건부 렌더링 익히기',
completed: true,
},
{
id: 'task-2',
title: 'TaskFlow 목록 만들기',
completed: false,
},
];
function App() {
return (
<main className="app">
<Header />
<TaskList tasks={tasks} />
</main>
);
}
export default App;
src/components/Header.tsx
export default function Header() {
return (
<header>
<p className="eyebrow">React Learning Project</p>
<h1>TaskFlow</h1>
<p>오늘 할 일을 하나씩 정리해 보세요.</p>
</header>
);
}
src/components/TaskItem.tsx
import type { Task } from '../types/task';
type TaskItemProps = {
task: Task;
};
export default function TaskItem({ task }: TaskItemProps) {
return (
<li>
<span>{task.title}</span>
<span>{task.completed ? '완료' : '진행 중'}</span>
</li>
);
}
src/components/TaskList.tsx
import TaskItem from './TaskItem';
import type { Task } from '../types/task';
type TaskListProps = {
tasks: Task[];
};
export default function TaskList({ tasks }: TaskListProps) {
if (tasks.length === 0) {
return (
<section>
<h2>작업 목록</h2>
<p>등록된 작업이 없습니다.</p>
</section>
);
}
return (
<section>
<h2>작업 목록</h2>
<ul>
{tasks.map((task) => (
<TaskItem key={task.id} task={task} />
))}
</ul>
</section>
);
}
src/index.css
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: Arial, sans-serif;
color: #111;
background: #f5f5f5;
}
.app {
width: min(720px, calc(100% - 32px));
margin: 48px auto;
}
.eyebrow {
color: #666;
font-size: 14px;
}
ul {
padding-left: 20px;
}
button, input, select { min-height: 44px; font: inherit; }
button, input, select { border: 1px solid #aaa; padding: 8px 12px; }
button { cursor: pointer; }
button:disabled { cursor: wait; opacity: .6; }
ul { list-style: none; padding: 0; display: grid; gap: 12px; }
.task-card { border: 1px solid #ccc; padding: 16px; display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.task-card__content, .task-card__actions { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
form, section { margin-block: 20px; }
input:not([type="checkbox"]), select { max-width: 100%; }
details { margin-block: 24px; } summary { cursor: pointer; min-height: 44px; }
body { overflow-wrap: anywhere; }
[role="dialog"], [data-radix-popper-content-wrapper] { --radius: 0; }
src/main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);
src/types/task.ts
export type Task = {
id: string;
title: string;
completed: boolean;
};
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"types": [
"vite/client"
],
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": [
"src",
"vite.config.ts"
]
}
vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({ plugins: [react()] });
글 소개·학습 준비·전체 목차

React · 4/32 · TaskFlow 누적 실습
React JSX 문법 사용법 조건부 렌더링과 리스트 처리
React · 4 / 32 · 필수 학습 · JSX
JSX는 React에서 화면 구조를 표현하는 문법입니다. HTML과 비슷해 보이지만 JavaScript 표현식을 함께 사용할 수 있고, 조건에 따라 다른 UI를 보여주거나 배열을 화면 목록으로 바꿀 수 있다는 점이 핵심입니다.
상단에서는 JSX 문법, 조건부 렌더링, 리스트 처리만 독립적으로 설명합니다. 하단에서는 TaskFlow의 정적 작업 목록을 실제 배열 데이터 기반으로 바꿉니다.
이번 글에서 배우는 내용
- JSX 기본 규칙
- JavaScript 표현식을 JSX에 넣는 방법
- 삼항 연산자와 &&를 이용한 조건부 렌더링
- 배열 map으로 목록을 렌더링하는 방법
- 빈 목록을 다른 화면으로 표시하는 방법
- JSX에서 자주 발생하는 문법 오류
목차
다음 학습
현재 TaskList와 TaskItem은 이미 최소한의 props를 사용하고 있습니다. 다음 글에서는 props를 본격적으로 다루면서 부모가 데이터를 소유하고 자식이 읽기 전용 입력값과 이벤트 함수를 받는 구조를 명확히 만듭니다.
공식 문서
이 글이 도움이 되었나요?
읽음 기록
읽음 — / 전체 33개 · 필수 27개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.