리액트 튜토리얼: 초보자를 위한 가이드

Audrey Lopez

Audrey Lopez

13 June 2025

리액트 튜토리얼: 초보자를 위한 가이드

예비 React 개발자 여러분, 환영합니다! 훌륭한 선택을 하셨습니다. React는 사용자 인터페이스 구축을 위한 강력하고 인기 있는 JavaScript 라이브러리이며, 이를 배우는 것은 웹 개발 기술을 향상시키는 확실한 방법입니다. 이 포괄적인 단계별 가이드는 여러분을 초보자에서 전문가로 이끌어, 2025년에 자신만의 React 애플리케이션을 구축하는 데 필요한 실용적인 지식을 제공할 것입니다. 읽는 것뿐만 아니라 실습에 집중할 것이므로, 코드를 작성할 준비를 하세요!

💡
멋진 API 문서를 생성하는 훌륭한 API 테스트 도구를 원하시나요?

개발팀이 최대의 생산성으로 함께 작업할 수 있는 통합된 올인원 플랫폼을 원하시나요?

Apidog는 여러분의 모든 요구를 충족하며, Postman을 훨씬 저렴한 가격으로 대체합니다!
button

React 개발 환경을 설정해 봅시다

멋진 사용자 인터페이스를 구축하기 전에 작업 공간을 설정해야 합니다. 새 프로젝트를 시작하기 전에 작업장을 준비하는 것으로 생각하세요.

필수 설치: Node.js 및 npm

React 애플리케이션은 Node.js와 그 패키지 관리자인 npm(Node Package Manager)을 사용하여 구축 및 관리됩니다.

시작하려면 공식 Node.js 웹사이트로 이동하여 최신 LTS(Long-Term Support) 버전을 다운로드하세요. 설치 프로그램은 간단합니다. 화면의 지침을 따르기만 하면 됩니다. 설치가 완료되면 Node.js와 npm 모두 사용할 준비가 됩니다. 터미널 또는 명령 프롬프트를 열고 다음을 입력하여 설치를 확인할 수 있습니다:Bash

node -v
npm -v

이 명령은 설치된 Node.js 및 npm의 버전을 각각 출력합니다.

Vite를 사용한 첫 번째 React 프로젝트

과거에는 새로운 React 프로젝트를 시작하는 데 create-react-app이 기본 도구였습니다. 하지만 현대 웹 개발 환경은 빠르게 변화하며, 2025년에는 놀라운 속도와 효율성 때문에 Vite가 권장되는 선택입니다.

Vite로 새로운 React 프로젝트를 생성하려면 터미널을 열고 다음 명령을 실행하세요:Bash

npm create vite@latest my-first-react-app -- --template react

이 명령을 분석해 봅시다:

명령이 완료되면 프로젝트 이름으로 된 새 디렉토리가 생성됩니다. 이 디렉토리로 이동하세요:Bash

cd my-first-react-app

다음으로 프로젝트의 종속성을 설치해야 합니다. 이것은 React 애플리케이션이 제대로 작동하는 데 필요한 다른 패키지들입니다. 이 명령을 실행하세요:Bash

npm install

마지막으로, 새로 만든 React 애플리케이션이 작동하는 것을 보려면 개발 서버를 시작하세요:Bash

npm run dev

터미널에 로컬 URL(일반적으로 http://localhost:5173)이 표시됩니다. 웹 브라우저에서 이 URL을 열면 Vite가 생성한 기본 React 애플리케이션을 볼 수 있습니다. 축하합니다! 첫 번째 React 프로젝트를 설정했습니다!


React의 핵심: 컴포넌트와 JSX

이제 React 애플리케이션이 실행되고 있으니, React를 강력하게 만드는 핵심 개념인 컴포넌트JSX에 대해 알아보겠습니다.

컴포넌트란 무엇인가요?

핵심적으로 React 애플리케이션은 컴포넌트라고 불리는 재사용 가능하고 독립적인 UI 조각들의 모음입니다. 웹 페이지가 레고 블록으로 만들어진다고 생각해보세요. 각 블록은 컴포넌트이며, 이를 조합하여 더 복잡한 구조를 만들 수 있습니다.

my-first-react-app 프로젝트에서 src 폴더를 열면 App.jsx라는 파일이 있습니다. 이것이 메인 애플리케이션 컴포넌트입니다. 기본 사항을 이해하기 위해 내용을 단순화해 봅시다:JavaScript

// src/App.jsx

function App() {
  return (
    <div>
      <h1>Hello, React World!</h1>
      <p>This is my very first React component.</p>
    </div>
  );
}

export default App;

이 코드에서:

JSX 이해하기: JavaScript XML

JSX는 JavaScript 파일 내1에 HTML과 유사한 코드를 작성할 수 있도록 하는 JavaScript의 구문 확장입니다. 실제 HTML은 아니지만, UI 코드를 작성하는 것을 훨씬 직관적이고 읽기 쉽게 만듭니다.

내부적으로 트랜스파일러(여기서는 Vite에 의해 구동됨)라는 도구가 이 JSX를 브라우저가 이해할 수 있는 일반 JavaScript로 변환합니다.

JSX의 강력함을 확인하기 위해 App.jsx를 수정해 봅시다. 중괄호 {}를 사용하여 JavaScript 표현식을 JSX 내에 직접 포함할 수 있습니다:JavaScript

// src/App.jsx

function App() {
  const name = "Beginner Developer";
  const year = new Date().getFullYear();

  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>Welcome to your React journey in {year}.</p>
    </div>
  );
}

export default App;

파일을 저장하면 브라우저가 자동으로 업데이트되어 새로운 콘텐츠를 표시합니다. 이것은 Vite의 기능 중 하나인 HMR(Hot Module Replacement)이며, 환상적인 개발 경험을 제공합니다.

첫 번째 사용자 정의 컴포넌트 생성하기

자신만의 컴포넌트를 만들어 봅시다. src 폴더에 Greeting.jsx라는 새 파일을 생성하세요:JavaScript

// src/Greeting.jsx

function Greeting() {
  return (
    <h2>This is a greeting from my custom component!</h2>
  );
}

export default Greeting;

이제 이 새로운 Greeting 컴포넌트를 App.jsx 컴포넌트 내에서 사용해 봅시다:JavaScript

// src/App.jsx
import Greeting from './Greeting'; // Greeting 컴포넌트 임포트

function App() {
  const name = "Beginner Developer";
  const year = new Date().getFullYear();

  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>Welcome to your React journey in {year}.</p>
      <Greeting /> {/* Greeting 컴포넌트 사용 */}
    </div>
  );
}

export default App;

<Greeting />를 일반 HTML 태그처럼 임포트하고 사용함으로써, 여러 컴포넌트로 UI를 구성했습니다. 이것이 React 애플리케이션의 근본적인 구성 요소입니다.


Props로 데이터 전달하기

우리의 Greeting 컴포넌트는 약간 정적입니다. 다른 사람들에게 인사하고 싶다면 어떻게 해야 할까요? 여기서 props(properties의 약자)가 등장합니다. Props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 방법입니다.

Props로 컴포넌트 동적으로 만들기

Greeting.jsx를 수정하여 name prop을 받도록 해 봅시다:JavaScript

// src/Greeting.jsx

function Greeting(props) {
  return (
    <h2>Hello, {props.name}! This is a greeting from my custom component.</h2>
  );
}

export default Greeting;

이제 App.jsx에서 Greeting 컴포넌트에 name prop을 전달할 수 있습니다.

JavaScript

// src/App.jsx
import Greeting from './Greeting';

function App() {
  return (
    <div>
      <Greeting name="Alice" />
      <Greeting name="Bob" />
      <Greeting name="Charlie" />
    </div>
  );
}

export default App;

이제 각각 고유한 이름을 가진 세 가지 다른 인사를 볼 수 있습니다. Props를 사용하면 다른 데이터로 컴포넌트를 재사용할 수 있어 UI를 매우 유연하게 만들 수 있습니다.

일반적이고 현대적인 JavaScript 관행은 함수의 매개변수 목록에서 props 객체를 직접 구조 분해하는 것입니다. Greeting.jsx를 리팩토링해 봅시다:JavaScript

// src/Greeting.jsx

function Greeting({ name }) {
  return (
    <h2>Hello, {name}! This is a greeting from my custom component.</h2>
  );
}

export default Greeting;

이것은 동일한 결과를 얻지만 더 깔끔하고 간결한 코드입니다.


State로 컴포넌트 메모리 관리하기

props는 컴포넌트 트리 아래로 데이터를 전달하는 데 훌륭하지만, 컴포넌트가 자체 데이터를 기억하고 관리해야 할 때는 어떻게 될까요? 여기서 state가 등장합니다. State는 컴포넌트 내부에서 관리되는 데이터입니다. 컴포넌트의 state가 변경되면 React는 새로운 state를 반영하기 위해 해당 컴포넌트를 자동으로 다시 렌더링합니다.

useState Hook 소개

함수형 컴포넌트에서 state를 관리하기 위해 React의 특별한 함수인 Hook을 사용합니다. 가장 기본적인 Hook은 useState입니다.

useState가 어떻게 작동하는지 이해하기 위해 간단한 카운터 컴포넌트를 만들어 봅시다. src 폴더에 Counter.jsx라는 새 파일을 생성하세요:JavaScript

// src/Counter.jsx
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

export default Counter;

이것을 분석해 봅시다:

  1. count: state의 현재 값입니다.
  2. setCount: count state를 업데이트하는 데 사용할 수 있는 함수입니다.

이제 이 Counter 컴포넌트를 App.jsx에 추가해 봅시다:JavaScript

// src/App.jsx
import Counter from './Counter';

function App() {
  return (
    <div>
      <h1>My Awesome React App</h1>
      <Counter />
    </div>
  );
}

export default App;

이제 브라우저에서 카운터를 볼 수 있습니다. 버튼을 클릭할 때마다 숫자가 증가합니다. React는 Counter 컴포넌트의 state가 변경될 때마다 이를 다시 렌더링합니다.


사용자 동작에 응답하기: 이벤트 처리

상호 작용성은 현대 웹 애플리케이션의 핵심입니다. React는 클릭, 폼 제출, 키보드 입력과 같은 이벤트를 처리하는 간단하고 일관된 방법을 제공합니다.

Counter 컴포넌트에서 onClick을 사용한 기본적인 이벤트 핸들러를 이미 보았습니다. 사용자 입력을 받는 간단한 폼을 만들어 이것을 더 자세히 살펴보겠습니다.

NameForm.jsx라는 새 컴포넌트 파일을 생성하세요:JavaScript

// src/NameForm.jsx
import { useState } from 'react';

function NameForm() {
  const [name, setName] = useState('');

  const handleSubmit = (event) => {
    event.preventDefault(); // 기본 폼 제출 동작 방지
    alert(`Hello, ${name}!`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input
          type="text"
          value={name}
          onChange={(event) => setName(event.target.value)}
        />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

export default NameForm;

이 폼 컴포넌트를 분석해 봅시다:

NameFormApp.jsx에 추가하여 작동하는 것을 확인하세요.


정보 표시: 조건부 렌더링 및 목록

실제 애플리케이션은 특정 조건에 따라 콘텐츠를 표시하거나 숨겨야 하는 경우가 많으며, 데이터를 목록으로 표시해야 하는 경우도 자주 있습니다.

조건부 렌더링으로 표시 및 숨기기

사용자가 로그인했는지 여부에 따라 다른 메시지를 표시하는 컴포넌트를 만들어 봅시다. LoginMessage.jsx라는 파일을 생성하세요:JavaScript

// src/LoginMessage.jsx

function LoginMessage({ isLoggedIn }) {
  if (isLoggedIn) {
    return <h2>Welcome back!</h2>;
  }
  return <h2>Please log in.</h2>;
}

export default LoginMessage;

더 간결한 조건부 렌더링을 위해 삼항 연산자를 사용할 수도 있습니다:JavaScript

// src/LoginMessage.jsx

function LoginMessage({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn ? <h2>Welcome back!</h2> : <h2>Please log in.</h2>}
    </div>
  );
}

export default LoginMessage;

그런 다음 App.jsx에서 이 컴포넌트를 사용하고 isLoggedIn prop을 전달하여 다른 메시지를 확인할 수 있습니다.

데이터 목록 표시하기

항목 목록을 렌더링하려면 일반적으로 배열의 map() 메서드를 사용합니다. 과일 목록을 표시하는 컴포넌트를 만들어 봅시다. FruitList.jsx라는 파일을 생성하세요:JavaScript

// src/FruitList.jsx

function FruitList() {
  const fruits = ['Apple', 'Banana', 'Cherry', 'Date'];

  return (
    <div>
      <h3>My Favorite Fruits:</h3>
      <ul>
        {fruits.map((fruit, index) => (
          <li key={index}>{fruit}</li>
        ))}
      </ul>
    </div>
  );
}

export default FruitList;

여기서 우리는 fruits 배열을 순회하며 각 과일에 대해 <li> 요소를 반환하고 있습니다.

key={index} prop에 주목하세요. 항목 목록을 렌더링할 때 React는 목록이 변경될 때 효율적으로 업데이트하기 위해 각 항목에 고유한 key가 필요합니다. 배열 인덱스를 key로 사용하는 것은 간단하고 정적인 목록에는 허용됩니다. 하지만 항목이 추가, 제거 또는 재정렬될 수 있는 동적인 목록의 경우, 가능하다면 데이터에서 고유 ID를 사용하는 것이 가장 좋습니다.


애플리케이션에 스타일 추가하기

훌륭한 애플리케이션은 보기 좋아야 합니다. React 컴포넌트에 스타일을 적용하는 몇 가지 방법이 있습니다.

CSS 스타일시트

가장 간단한 방법은 일반 CSS 파일을 사용하는 것입니다. src 폴더에 App.css 파일이 있습니다. 여기에 스타일을 추가할 수 있습니다.

예를 들어, FruitList 컴포넌트에 스타일을 적용하려면 App.css에 다음을 추가할 수 있습니다:CSS

/* src/App.css */

.fruit-list {
  list-style-type: none;
  padding: 0;
}

.fruit-item {
  background-color: #f0f0f0;
  margin: 5px 0;
  padding: 10px;
  border-radius: 5px;
}

그런 다음 FruitList.jsx에서 이러한 CSS 클래스를 사용할 수 있습니다:JavaScript

// src/FruitList.jsx
import './App.css'; // CSS 파일을 임포트해야 합니다

function FruitList() {
  const fruits = ['Apple', 'Banana', 'Cherry', 'Date'];

  return (
    <div>
      <h3>My Favorite Fruits:</h3>
      <ul className="fruit-list">
        {fruits.map((fruit, index) => (
          <li key={index} className="fruit-item">{fruit}</li>
        ))}
      </ul>
    </div>
  );
}

export default FruitList;

JSX에서는 class 대신 className을 사용한다는 점에 유의하세요. class는 JavaScript의 예약어이기 때문입니다.

CSS 모듈

더 큰 애플리케이션의 경우, CSS 모듈은 특정 컴포넌트에 스타일 범위를 지정하여 스타일 충돌을 방지하는 방법을 제공합니다. CSS 모듈 파일은 .module.css 확장자로 이름이 지정됩니다.

FruitList.module.css를 만들어 봅시다:CSS

/* src/FruitList.module.css */

.list {
  list-style-type: square;
}

.item {
  color: blue;
}

이제 FruitList.jsx에서 스타일을 객체로 임포트합니다:JavaScript

// src/FruitList.jsx
import styles from './FruitList.module.css';

function FruitList() {
  const fruits = ['Apple', 'Banana', 'Cherry', 'Date'];

  return (
    <div>
      <h3>My Favorite Fruits (Styled with CSS Modules):</h3>
      <ul className={styles.list}>
        {fruits.map((fruit, index) => (
          <li key={index} className={styles.item}>{fruit}</li>
        ))}
      </ul>
    </div>
  );
}

export default FruitList;

Vite는 자동으로 고유한 클래스 이름을 생성하여 FruitList.module.css의 스타일이 FruitList 컴포넌트에만 적용되도록 합니다.


React Router로 앱 탐색하기

대부분의 웹 애플리케이션은 여러 페이지를 가집니다. React로 구축된 SPA(단일 페이지 애플리케이션)에서 이러한 "페이지" 간의 탐색을 처리하기 위해 React Router라는 라이브러리를 사용합니다.

React Router 설정하기

먼저 React Router를 설치해야 합니다:Bash

npm install react-router-dom

페이지 생성하기

두 개의 간단한 페이지 컴포넌트를 만들어 봅시다: HomePage.jsxAboutPage.jsx:JavaScript

// src/HomePage.jsx

function HomePage() {
  return (
    <div>
      <h1>Home Page</h1>
      <p>Welcome to the home page of our amazing app!</p>
    </div>
  );
}

export default HomePage;

JavaScript

// src/AboutPage.jsx

function AboutPage() {
  return (
    <div>
      <h1>About Page</h1>
      <p>This is all about our incredible application.</p>
    </div>
  );
}

export default AboutPage;

라우터 구성하기

이제 App.jsx에서 경로를 구성하겠습니다:JavaScript

// src/App.jsx
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import HomePage from './HomePage';
import AboutPage from './AboutPage';

function App() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/about">About</Link>
            </li>
          </ul>
        </nav>

        <hr />

        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/about" element={<AboutPage />} />
        </Routes>
      </div>
    </Router>
  );
}

export default App;

React Router의 새로운 컴포넌트를 분석해 봅시다:

이제 "Home" 및 "About" 링크를 클릭하면 전체 페이지를 다시 로드하지 않고 콘텐츠가 변경됩니다. 클라이언트 측 라우팅을 성공적으로 구현했습니다!


더 나아가기: useEffect Hook

useState Hook은 렌더링되는 내용에 직접 영향을 미치는 state를 관리하는 데 사용됩니다. 하지만 API에서 데이터를 가져오거나, 구독을 설정하거나, DOM을 수동으로 변경하는 것과 같은 부작용(side effects)은 어떻게 처리할까요? 이를 위해 useEffect Hook을 사용합니다.

useEffect Hook은 기본적으로 모든 렌더링 후에 실행됩니다. 가짜 API에서 데이터를 가져오는 컴포넌트를 만들어 이것이 어떻게 작동하는지 살펴봅시다.

새 파일 DataFetcher.jsx를 생성하세요:JavaScript

// src/DataFetcher.jsx
import { useState, useEffect } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 이 함수는 컴포넌트가 렌더링된 후에 호출됩니다
    const fetchData = async () => {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
        const jsonData = await response.json();
        setData(jsonData);
      } catch (error) {
        console.error('Error fetching data:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []); // 빈 의존성 배열이 중요합니다!

  if (loading) {
    return <p>Loading...</p>;
  }

  return (
    <div>
      <h3>Fetched Data:</h3>
      <h4>{data.title}</h4>
      <p>{data.body}</p>
    </div>
  );
}

export default DataFetcher;

여기서 useEffect를 이해하는 핵심은 두 번째 인자인 의존성 배열입니다.

컴포넌트가 로드될 때 데이터를 가져오고 표시하는 것을 확인하기 위해 DataFetcherApp.jsx에 추가하세요.

결론 및 다음 단계

많은 것을 배웠습니다! 다음 방법을 배웠습니다:

이것은 큰 성과이며, 이제 구축할 수 있는 견고한 기반을 갖게 되었습니다. React의 세계는 광대하고 흥미롭습니다. 다음으로 탐색해 볼 만한 몇 가지 주제는 다음과 같습니다:

지금 할 수 있는 가장 중요한 것은 계속 구축하는 것입니다. 연습이 핵심입니다. 매일 사용하는 간단한 웹사이트나 애플리케이션을 다시 만들어 보세요. 새로운 기능에 도전해 보세요. 코딩을 더 많이 할수록 더 자신감 있고 숙련될 것입니다.

React 커뮤니티에 오신 것을 환영합니다. 즐거운 코딩 되세요!

💡
멋진 API 문서를 생성하는 훌륭한 API 테스트 도구를 원하시나요?

개발팀이 최대의 생산성으로 함께 작업할 수 있는 통합된 올인원 플랫폼을 원하시나요?

Apidog는 여러분의 모든 요구를 충족하며, Postman을 훨씬 저렴한 가격으로 대체합니다!
button

Apidog에서 API 설계-첫 번째 연습

API를 더 쉽게 구축하고 사용하는 방법을 발견하세요