본문 바로가기
FE/React

2. 실전 프로젝트: TodoList 웹앱 개발하기

by aeyong-dev 2023. 7. 19.

이제는 실제로 TodoList를 만들어보겠습니다.

프로젝트 준비, UI구성, 기능 구현의 순으로 개발을 진행할게요. 


2.1 프로젝트 준비

todo-app 이라는 이름의 프로젝트를 만들겠습니다. 

$ npx create-react-app todo-app

이라고 터미널에 입력하면 todo-app 이라는 디렉토리가 생기고, 그 아래에 리액트 프로젝트가 생기는 것 정도는 이제 아실거라고 생각합니다.

스타일링은 Sass를 사용할 것이므로, 다음과 같이 입력하여 Sass도 설치합니다. 

$ yarn add sass classnames react-icons

Prettier를 사용하여 코드 스타일을 정리해보자구요.

최상위 디렉토리에 .prettierrc 파일을 생성하고 다음과 같이 작성합니다. 

{
  "singleQuote": true,
  "semi": true,
  "useTabs": false,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 80
}

글로벌 스타일이 적용되어있는 index.css의 내용을 수정하겠습니다. 

body {
  margin: 0;
  padding: 0;
  background: #e9ecef;
}

app.js의 app 컴포넌트의 내용을 모두 지웁니다. 

function App() {
  return <div>TodoList를 작성해보자</div>;
}

export default App;

이제 모든 설정이 끝났습니다. 진짜로 시작해볼까요?


2.2 UI 구성

앞으로 만들 컴포넌트에 대해 설명하겠습니다. 

  1. TodoTemplate: 화면을 가운데에 정렬, 앱 타이틀을 보여줌. children으로 JSX를 props로 받아와 렌더링.
  2. TodoInsert: 새로운 항목을 입력하고 추가. state로 input의 상태를 관리.
  3. TodoListItem: 항목에 대한 정보. todo객체를 props로 받아와 상태에 따른 UI를 보여줌.
  4. TodoList: todos배열을 props로 받아와 map을 사용하여 여러개의 TodoListItem 컴포넌트로 변환하여 보여줌.

이렇게 4개의 컴포넌트로 나누었습니다. 

컴포넌트들은 src 아래의 components 디렉토리에 저장합니다. 

이것이 국룰입니다. 

컴포넌트 끼리 디렉토리 안에 모아놓으면 편하니까요. 

 

이제 기능보다 모양, UI에 집중하여 작성해보겠습니다. 

2.2.1 TodoTemplate

src/components 디렉토리 안에 TodoTemplate.js와 TodoTemplate.scss 파일을 생성합니다. 

TodoTemplate.js를 다음과 같이 작성합니다. 

import './TodoTemplate.scss';

const TodoTemplate = ({ children }) => {
  return (
    <div className="TodoTemplate">
      <div className="app-title">일정 관리</div>
      <div className="content">{children}</div>
    </div>
  );
};

export default TodoTemplate;

그리고 App.js에서 불러와 렌더링합시다. 

import TodoTemplate from "./components/TodoTemplate";

function App() {
  return <TodoTemplate>TodoList를 작성해보자</TodoTemplate>;
}

export default App;

이제 scss를 작성할게요.

.TodoTemplate{
    width: 512px;
    margin-left: auto;
    margin-right: auto;
    margin-top: 6rem;
    border-radius: 4px;
    overflow: hidden;

    .app-title{
        background: #22b8cf;
        color: white;
        height: 4rem;
        font-size: 1.5rem;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    .content{
        background: white;
    }
}

다음과 같이 화면이 구성될 것입니다. 참 쉽죠?

display: flex 속성을 자주 사용할 것입니다. 

이를 위해서는 다음 웹 사이트를 방문하여 더 공부할 수 있습니다. 

http://flexboxfroggy.com/#ko 

 

Flexbox Froggy

A game for learning CSS flexbox

flexboxfroggy.com

2.2.2 TodoInsert 만들기

마찬가지로, components 폴더에 TodoInsert.js와 TodoInsert.scss 파일을 생성할게요.

그리고 TodoInsert.js에 다음과 같이 작성합니다. 

import { MdAdd } from 'react-icons/md';
import './TodoInsert.scss';

const TodoInsert = () => {
  return (
    <form className="TodoInsert">
      <input placeholder="할 일을 입력하세요" />
      <button type="submit">
        <MdAdd />
      </button>
    </form>
  );
};
export default TodoInsert;

그런데 여기서 사용한 react-icons/md의 MdAdd가 무엇일까요? 

https://react-icons.netlify.com/#/icons/md 페이지에는 다양한 아이콘이 있습니다. 

여기서 사용하고 싶은 아이콘을 import 해서 컴포넌트처럼 사용할 수 있어요.

 

이제 App에서 이 컴포넌트를 렌더링 해올게요.

import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';

function App() {
  return (
    <TodoTemplate>
      <TodoInsert />
    </TodoTemplate>
  );
}

export default App;

이제 스타일링을 해보겠습니다. 

이런식으로 교재를 따라 진행하였습니다. 

단순히 코드 내용을 복붙해서 올리는건 크게 의미가 없을 것 같아요.

코드를 치면서 새롭게 배우게 되는 것, 의문점들을 적어보겠습니다.


여차저차 해서, 스타일링은 끝났습니다.

UI적인 부분을 다루어서, 크게 어려운 내용은 없습니다. 


2.3 기능 구현하기

이제 실제 기능적인 부분을 다뤄볼게요.

2.3.1 App에서 todos 상태 사용

나중에 사용자가 직접 추가할 상태들은 모두 App 컴포넌트에서 관리합니다. 

App에서 useState로 상태를 관리하고, todos를 TodoList의 props로 전달해볼게요.

import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import { useState } from 'react';

function App() {
  const [todos, setTodos] = useState([
    {
      id: 1,
      text: '리액트의 기초 알아보기',
      checked: true,
    },
    {
      id: 2,
      text: '컴포넌트 스타일링해 보기',
      checked: true,
    },
    {
      id: 3,
      text: '일정 관리 앱 만들어 보기',
      checked: false,
    },
  ]);

  return (
    <TodoTemplate>
      <TodoInsert />
      <TodoList todos={todos} />
    </TodoTemplate>
  );
}

export default App;

 

 

그리고 아래는 TodoList.js입니다. 

import TodoListItem from './TodoListItem';
import './TodoList.scss';

const TodoList = (todos) => {
  return (
    <div className="TodoList">
      {todos.map((todo) => (
        <TodoListItem todo={todo} key={todo.id} />
      ))}
    </div>
  );
};

export default TodoList;

 

  1. props로 todos 배열을 받아와
  2. 내장함수 map을 통해 TodoListItem으로 변환하여
  3. 렌더링했습니다. 

map으로 컴포넌트를 변환할 때는 key, props를 전달해줘야 합니다. 

여기서 key로는 todo의 고윳값인 id, props는 todo 배열 전체를 주었습니다. 

 

이제는 TodoListItem 컴포넌트에서 받아온 todo 값에 따라 UI를 보여줄 수 있도록 컴포넌트를 수정하겠습니다. 

TodoListItem.js를 다음과 같이 수정해요.

import {
  MdCheckBoxOutlineBlank,
  MdCheckBox,
  MdRemoveCircleOutline,
} from 'react-icons/md';
import cn from 'classnames';
import './TodoListItem.scss';

const TodoListItem = () => {
    const { text, checked } = todo;
  return (
    <div className="TodoListItem">
      <div className={cn('checkbox', {checked})}>
        {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
        <div className="text">{text}</div>
      </div>
      <div className="remove">
        <MdRemoveCircleOutline />
      </div>
    </div>
  );
};

export default TodoListItem;

이제 TodoLlist 컴포넌트가 App에서 전달해준 todos 값에 따라 다른 내용을 전달해줍니다. 

삼항연산자를 사용하여 각자 다른 값을 보여줘요.

잘 되는 모습을 확인할 수 있습니다.