첫 번째 React 앱 만들기
Vite 프로젝트의 파일 구조를 살펴보고 App 컴포넌트의 JSX와 CSS를 수정해 첫 React 화면을 완성합니다.
이 장에서는 그 기본 틀 위에서 여러분만의 첫 번째 리액트 앱을 만들어 봅니다.
리액트 코드가 실제로 어떻게 웹 페이지에 나타나는지 직접 경험해 볼 것입니다.
우리는 주로 src 폴더 안의 App.jsx 파일을 수정하며 리액트 컴포넌트를 작성하게 될 것입니다.
아직 모든 코드를 이해하지 못해도 괜찮습니다.
지금은 이것을 수정하면 웹 페이지가 이렇게 바뀌는구나! 하고 직관적으로 느껴보는 것이 중요합니다.
앱 부트스트랩과 개발 피드백 — 상태 추적
실제 첫 root 연결과 App JSX 반환/commit 뒤 개발 모듈 갱신의 상태가 달라진다. Fast Refresh가 main을 매번 실행한다는 가정을 피하고 기존 DOM/화면 값을 추적할 수 있다.
React · UI와 실행의 관계
main.jsx는 React 루트를 만들고 App의 첫 렌더를 요청합니다
HTML DOM은 index.html의 #root 대상, React root는 main.jsx가 만든 런타임 루트입니다. App은 JSX를 반환하며 React가 DOM에 반영합니다. Vite의 개발 모듈 갱신과 첫 실행을 구분합니다.
실선은 전달·렌더 요청·DOM 반영, 점선은 JSX 반환과 개발 모듈 갱신입니다. 저장 시 지원되는 변경은 Fast Refresh로 반영하며 매번 main.jsx 재실행을 전제로 하지 않습니다. 모든 Vite 변경이 이 갱신만으로 끝난다는 뜻도 아닙니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 관계·조건 설명 | 참여자 4명·메시지 8개의 앱 부트스트랩과 개발 갱신 순서다. HTML의 root 전달→React root의 App 렌더 요청→JSX 반환→HTML DOM commit, 이후 Vite 모듈 갱신→App 재렌더→JSX 반환→변경 commit이다. HTML은 DOM 대상을 가리키며 컴포넌트 계산은 React가 수행한다. 점선은 JSX 반환/개발 모듈 갱신이다. Fast Refresh가 매번 main.jsx를 재실행한다는 전제는 없다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 첫 root 연결 | root: DOM 대상 연결 / App: 첫 UI 계산 | main.jsx가 HTML #root 대상에 App 렌더를 요청한다 | HTML과 React의 계산 책임을 구분한다 |
| 첫 commit | UI: 첫 화면 / Vite: 개발 서버 연결 | App JSX 반환을 필요한 DOM에 반영한다 | JSX 반환과 DOM 반영은 별개다 |
| 지원되는 저장 갱신 | App: 변경 UI / main.jsx: 매번 재실행 전제 없음 | Vite 변경 모듈로 App을 다시 계산해 달라진 DOM을 commit한다 | Fast Refresh 지원 범위 밖은 전체 새로고침일 수 있다 |
앱 부트스트랩과 개발 피드백 — 상태 추적
실제 첫 root 연결과 App JSX 반환/commit 뒤 개발 모듈 갱신의 상태가 달라진다. Fast Refresh가 main을 매번 실행한다는 가정을 피하고 기존 DOM/화면 값을 추적할 수 있다.
React · UI와 실행의 관계
main.jsx는 React 루트를 만들고 App의 첫 렌더를 요청합니다
HTML DOM은 index.html의 #root 대상, React root는 main.jsx가 만든 런타임 루트입니다. App은 JSX를 반환하며 React가 DOM에 반영합니다. Vite의 개발 모듈 갱신과 첫 실행을 구분합니다.
실선은 전달·렌더 요청·DOM 반영, 점선은 JSX 반환과 개발 모듈 갱신입니다. 저장 시 지원되는 변경은 Fast Refresh로 반영하며 매번 main.jsx 재실행을 전제로 하지 않습니다. 모든 Vite 변경이 이 갱신만으로 끝난다는 뜻도 아닙니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 관계·조건 설명 | 참여자 4명·메시지 8개의 앱 부트스트랩과 개발 갱신 순서다. HTML의 root 전달→React root의 App 렌더 요청→JSX 반환→HTML DOM commit, 이후 Vite 모듈 갱신→App 재렌더→JSX 반환→변경 commit이다. HTML은 DOM 대상을 가리키며 컴포넌트 계산은 React가 수행한다. 점선은 JSX 반환/개발 모듈 갱신이다. Fast Refresh가 매번 main.jsx를 재실행한다는 전제는 없다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 첫 root 연결 | root: DOM 대상 연결 / App: 첫 UI 계산 | main.jsx가 HTML #root 대상에 App 렌더를 요청한다 | HTML과 React의 계산 책임을 구분한다 |
| 첫 commit | UI: 첫 화면 / Vite: 개발 서버 연결 | App JSX 반환을 필요한 DOM에 반영한다 | JSX 반환과 DOM 반영은 별개다 |
| 지원되는 저장 갱신 | App: 변경 UI / main.jsx: 매번 재실행 전제 없음 | Vite 변경 모듈로 App을 다시 계산해 달라진 DOM을 commit한다 | Fast Refresh 지원 범위 밖은 전체 새로고침일 수 있다 |
프로젝트 구조 살펴보기
VS Code로 my-first-react-app 프로젝트를 열고 왼쪽 탐색기(Explorer)를 보면 다음과 같은 폴더 및 파일 구조를 확인할 수 있습니다.
이 중에서 우리가 주로 관심을 가질 파일은 index.html, src/main.jsx, 그리고 src/App.jsx입니다.
index.html: 이 파일은 여러분의 리액트 애플리케이션이 로드될 기본 HTML 페이지입니다.<div id="root"></div>라는 빈 태그를 찾아보세요. 리액트 애플리케이션은 이id="root"를 가진div안에 렌더링됩니다.src/main.jsx: 이 파일은 리액트 애플리케이션의 시작점입니다. 여기서App.jsx에서 가져온<App />컴포넌트를index.html의root요소에 연결합니다.src/App.jsx: 이 파일은 현재 웹 페이지에 보이는 내용을 담고 있는 메인 컴포넌트입니다. 우리는 주로 이 파일을 수정하면서 첫 번째 리액트 앱을 만들어 볼 것입니다.
index.html 안의 모듈 script가 main.jsx를 불러오고, main.jsx가 App을 import해 첫 렌더를 요청합니다. 파일 이름만 외우기보다 이 연결을 순서대로 찾으세요. JSX의 함수 컴포넌트는 App처럼 대문자로 시작하며, 단순히 함수를 선언하는 것만으로 브라우저에 연결되지는 않습니다.
App.jsx 수정하여 내용 변경하기
이제 VS Code에서 src/App.jsx 파일을 열어보세요.
기본 템플릿에는 Vite와 React 로고, 카운터 예제, HMR 안내 문구가 들어 있습니다.
처음에는 이 예제 코드를 단순한 화면으로 바꾸며 컴포넌트 함수가 반환한 JSX가 브라우저 화면이 된다는 점을 확인하면 됩니다.
이제 이 내용을 우리가 원하는 간단한 문구로 변경해 보겠습니다.
기존 내용 삭제 및 새 내용 추가
기본 예제에서 로고와 카운터 관련 import를 지우고, 다음과 같이 간단한 <h1> 태그와 <p> 태그를 반환해 보세요.
import './App.css';
function App() {
return (
<div className="App">
{/* 여기에 새로운 내용을 작성합니다 */}
<h1>나 혼자 React!</h1>
<p>첫 번째 React 앱을 만들고 있습니다.</p>
<p>정말 재미있네요!</p>
</div>
);
}
export default App;파일 저장:
코드를 수정한 후 Ctrl+S (macOS: Cmd+S)를 눌러 파일을 저장합니다.
결과 확인:
개발 서버(npm run dev로 실행했던)가 여전히 실행 중이라면, 앞 절의 터미널에 표시된 실제 Local 주소에서 App.jsx를 저장한 뒤 화면 변화를 확인하세요. 반영되지 않으면 파일 저장 여부와 개발 서버, 브라우저의 첫 오류를 확인하세요.
화면에 나 혼자 React!, 첫 번째 React 앱을 만들고 있습니다., 정말 재미있네요!라는 문구가 보인다면 성공입니다!
JSX에 대하여 잠시 알아보기
App.jsx 파일에서 return 문 안에 작성했던 <div className="App"> ... </div>와 같은 코드들을 보셨을 것입니다.
이것은 HTML처럼 생겼지만 사실은 JSX(JavaScript XML)라고 부르는, React 컴포넌트에서 자주 쓰는 자바스크립트 문법 확장입니다.
JSX는 자바스크립트 안에 HTML(XML)과 유사한 문법을 사용하여 UI를 기술할 수 있도록 해줍니다.
언뜻 보면 HTML과 매우 유사하지만, 다음과 같은 몇 가지 중요한 차이점이 있습니다.
-
HTML 속성 이름: HTML의
class와for에 대응하는className,htmlFor처럼 이 React DOM 예제에서는 정해진 프로퍼티 이름을 사용합니다. 많은 이름은onClick처럼 camelCase이며,aria-*와data-*는 하이픈 표기를 유지합니다. HTML의class·for와 JSX 표기를 구별하며, 모든 속성을 하나의 이름 변환 규칙으로 추측하지 않습니다. -
단일 루트 요소: 여러 JSX 태그를 나란히 하나의 표현식으로 반환할 때는 묶음이 필요합니다. 예를 들어, 여러 태그를 나란히 반환하고 싶다면 하나의
<div>나<>(프래그먼트)로 감싸야 합니다.// ⭕ 올바른 JSX (하나의 div로 감싸져 있음) function MyComponent() { return ( <div> <h1>제목</h1> <p>내용</p> </div> ); } // ❌ 잘못된 JSX (두 개의 최상위 요소) // function MyComponent() { // return ( // <h1>제목</h1> // <p>내용</p> // ); // }<div>로 감싸는 것이 번거롭거나 불필요한 DOM 노드를 생성하고 싶지 않을 때는<></>와 같은 빈 태그를 사용할 수 있는데, 이를 프래그먼트(Fragment)라고 부릅니다.// ⭕ 올바른 JSX (프래그먼트 사용) function MyComponent() { return ( <> <h1>제목</h1> <p>내용</p> </> ); } -
자바스크립트 표현식 삽입: JSX 내부에서 중괄호
{}를 사용하면 자바스크립트 표현식을 삽입할 수 있습니다. 예를 들어, 변수의 값이나 함수의 결과 등을 UI에 바로 렌더링할 수 있습니다.function Welcome() { const name = '독자 여러분'; return ( <h1>안녕하세요, {name}!</h1> ); }JSX는 자바스크립트 안에서 UI 구조를 선언할 수 있게 해 줍니다.
이후 예제에서는 JSX로 리액트 컴포넌트를 작성합니다.
스타일 적용하기
현재 여러분의 웹 페이지는 글씨가 작고 정렬이 되어 있지 않아 보기에 좋지 않을 수 있습니다.
리액트 컴포넌트에 스타일을 적용하는 방법은 여러 가지가 있지만, 여기서는 Vite 기본 프로젝트에서 바로 사용할 수 있는 일반 CSS 파일인 App.css를 수정해 보겠습니다.
App.css 파일 수정:
VS Code에서 src/App.css 파일을 열어 기존의 모든 내용을 삭제하고 다음과 같이 간단한 스타일을 추가해 보세요.
.App {
text-align: center; /* 텍스트를 가운데 정렬 */
background-color: #f0f0f0; /* 배경색 변경 */
min-height: 100vh; /* 최소 높이를 뷰포트 높이로 설정 */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin); /* 폰트 크기 조정 */
color: #333; /* 텍스트 색상 변경 */
}
h1 {
color: #007bff; /* 제목 색상 파란색 */
margin-bottom: 20px;
}
p {
color: #555; /* 단락 텍스트 색상 회색 */
line-height: 1.5;
}파일 저장:
App.css 파일을 저장합니다.
결과 확인: 웹 브라우저를 확인하면 여러분이 추가한 스타일이 적용되어 텍스트가 가운데 정렬되고, 배경색과 폰트 색상이 변경된 것을 볼 수 있을 것입니다.
App.jsx 파일에서 import './App.css';라는 코드를 통해 CSS 파일을 불러왔기 때문에, 해당 스타일이 App 컴포넌트 화면에 적용됩니다.
이 Vite 프로젝트에서는 Vite가 CSS 파일 import를 처리하므로, 컴포넌트의 화면 구조와 스타일의 연결을 자바스크립트 파일에서 함께 추적할 수 있습니다.
기본 템플릿의 index.css에는 body의 정렬·색상·최소 폭 같은 전역 규칙이 남아 있을 수 있습니다. App.css만 바꾸고 화면 전체가 같은 모양이 되리라고 가정하지 않습니다. 이번 독립 실습에서는 main.jsx의 import './index.css'는 유지하고, index.css의 내용을 다음처럼 교체하여 전역 조건을 고정합니다. App.css의 원래 코드는 그대로 사용합니다.
* {
box-sizing: border-box;
}
body {
margin: 0;
min-width: 0;
font-family: system-ui, sans-serif;
}
#root {
width: 100%;
min-height: 100vh;
}
.App {
padding: 16px;
}이제 root의 기본 여백에 더해진 100vh가 아니라 App 자체가 화면 높이를 채웁니다. 좁은 화면에서 긴 제목은 자연스럽게 줄바꿈할 수 있고, 글자색과 배경색은 App.css가 정합니다. index.css의 .App은 공간을, App.css의 .App은 정렬과 색 등을 설정하므로 두 파일의 역할을 함께 읽으세요.
내용과 표현식을 따로 바꾸기
앞의 App.jsx에서 첫 번째 p만 오늘은 {2 + 3}번째 학습입니다.라는 JSX 내용으로 바꾸고 h1은 그대로 둡니다. 저장 뒤 보이는 첫 p와 h1을 먼저 예상하세요. 계산식 주위의 중괄호를 지우면 무엇이 표시되나요? App.css의 제목 색을 바꾸는 것만으로 학습 횟수도 달라지나요?
파일에서 화면까지의 풀이
첫 p는 ‘오늘은 5번째 학습입니다.’, h1은 ‘나 혼자 React!’입니다. 중괄호 없이 JSX 텍스트로 쓴 2 + 3은 계산되지 않아 ‘오늘은 2 + 3번째 학습입니다.’로 보입니다. 제목 색 변경은 CSS 표현만 바꾸며 p의 데이터 계산은 바꾸지 않습니다. 저장 후 반영되지 않으면 수정한 프로젝트·파일·Local 주소가 같은지와 첫 오류를 확인합니다. JSX의 반환과 CSS 적용은 서로 다른 경계지만 같은 화면으로 연결됩니다.
첫 React 앱에서는 프로젝트 구조를 통째로 외우기보다 진입 파일, App 컴포넌트, 스타일, 브라우저 화면이 이어지는 경로를 확인합니다.
이 연결이 보이면 다음 장부터는 JSX 안에서 데이터를 표현하고 이벤트로 상태를 바꾸는 흐름을 훨씬 자연스럽게 따라갈 수 있습니다.