FullStack FrontEnd

React Todo 앱 - 컴포넌트 분리하기

SBDevv 2026. 9. 17. 08:49

React Todo 앱 - 컴포넌트 분리하기

앞서 설계한 Todo 앱을 실제로 파일 단위로 쪼개서 구현한다. export/import로 컴포넌트를 분리하는 방법부터 TodoItem, TodoList를 만들고 App에서 연결하는 과정, 기본 레이아웃까지 다룬다.

컴포넌트를 파일로 분리하기 - import와 export

지금까지는 한 파일 안에 여러 컴포넌트를 함께 작성했다. 이제부터는 컴포넌트마다 별도의 파일로 나누고, export/import로 서로 연결한다.

// src/Greeting.jsx
function Greeting() {
  return <h2>안녕하세요!</h2>;
}

export default Greeting;
// src/App.jsx
import Greeting from "./Greeting";

function App() {
  return <Greeting />;
}

export default App;

Greeting.jsx에서 export default Greeting은 "이 컴포넌트를 다른 파일에서 가져다 쓸 수 있게 내보낸다"는 뜻이고, App.jsx에서 import Greeting from "./Greeting"은 "같은 폴더에 있는 Greeting.jsx 파일에서 내보낸 걸 가져온다"는 뜻이다.

import 경로에는 왜 ./를 붙이고, 확장자는 왜 안 써도 될까? ./는 "지금 이 파일과 같은 폴더에서 찾는다"는 뜻이다. ./가 없으면 React는 내가 만든 파일이 아니라 node_modules에 설치된 라이브러리를 찾으려고 한다. 확장자는 Vite가 자동으로 붙여서 찾아주기 때문에 생략해도 된다.

TodoItem.jsx 만들기

목록 안의 할 일 하나를 보여주는 컴포넌트다. 완료 여부에 따라 취소선만 표시해본다.

// src/TodoItem.jsx
function TodoItem({ todo }) {
  return (
    <li style={{ textDecoration: todo.completed ? "line-through" : "none" }}>
      {todo.text}
    </li>
  );
}

export default TodoItem;

todo라는 객체 하나를 props로 통째로 받아서, todo.text, todo.completed처럼 필요한 값을 꺼내 쓰고 있다.

TodoList.jsx 만들기

todos 배열 전체를 받아서, map()으로 TodoItem을 여러 개 렌더링하는 컴포넌트다.

// src/TodoList.jsx
import TodoItem from "./TodoItem";

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </ul>
  );
}

export default TodoList;

App.jsx에서 전체 연결하기

// src/App.jsx
import { useState } from "react";
import TodoList from "./TodoList";

function App() {
  const [todos, setTodos] = useState([
    { id: 1, text: "React 공부하기", completed: false },
    { id: 2, text: "장보기", completed: true },
    { id: 3, text: "운동하기", completed: false },
  ]);

  return (
    <div>
      <h1>Todo 리스트</h1>
      <TodoList todos={todos} />
    </div>
  );
}

export default App;

App이 가지고 있는 todos state를 TodoList에게 props로 내려주고, TodoList는 그걸 다시 TodoItem에게 하나씩 내려준다. 앞서 배운 "부모→자식 데이터 전달"이 파일이 나뉜 상태에서도 똑같이 적용된다.

기본 레이아웃과 스타일 적용하기

<div style={{ maxWidth: "400px", margin: "0 auto", padding: "24px" }}>
  <h1>Todo 리스트</h1>
  <TodoList todos={todos} />
</div>

maxWidth와 margin: "0 auto"로 화면 가운데에 적당한 너비로 정렬해서, 이후 입력창과 버튼을 추가했을 때도 보기 좋은 레이아웃을 미리 잡아둔다.

실습 정리

실습 1) TodoItem.jsx 파일 만들기 — todo props를 받아서 텍스트를 <li>로 보여주는 컴포넌트를 작성하고 export default로 내보낸다.

실습 2) TodoList.jsx 파일 만들기 — TodoItem을 import해서 todos props를 map()으로 순회하며 렌더링한다.

실습 3) App.jsx에서 연결해서 화면에 렌더링하기 — App.jsx에서 TodoList를 import하고 todos state를 props로 전달해서 화면에 할 일 3개가 나타나는지 확인한다.

실습 4) 기본 스타일 적용하기 — App의 최상위 <div>에 style을 추가해서 적당한 너비로 가운데 정렬되도록 꾸며본다.

마무리

  • export default 컴포넌트명으로 내보내고 import 컴포넌트명 from "./경로"로 가져오는 패턴이 파일 분리의 기본이다.
  • App → TodoList → TodoItem처럼 props가 여러 단계를 거쳐 내려가도, 부모→자식 전달 방식 자체는 하나의 파일에서 쓰던 것과 동일하다.
  • 컴포넌트를 파일로 나누면 각 파일이 하나의 역할만 담당하게 되어, 나중에 기능을 수정하거나 찾을 때 훨씬 편해진다.