본문 바로가기

카테고리 없음

[React] React로 할 일 관리 앱 만들기: 6장 - 디자인 개선 및 리팩토링

반응형

 

안녕하세요! 이번 포스트에서는 우리의 할 일 관리 앱의 디자인을 개선하고, 컴포넌트 구조를 리팩터링 하여 코드의 가독성

과 유지보수성을 높이는 방법을 알아보겠습니다.

1. CSS 스타일링 개선

먼저, 앱의 외관을 개선하기 위해 CSS를 수정해 보겠습니다. src 폴더에 TodoList.css 파일을 생성하고 다음 내용을 추가합니다:

.todo-app {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
  font-family: Arial, sans-serif;
}

.todo-app h2 {
  text-align: center;
  color: #333;
}

.todo-form {
  display: flex;
  margin-bottom: 20px;
}

.todo-form input[type="text"] {
  flex-grow: 1;
  padding: 10px;
  font-size: 16px;
  border: 1px solid #ddd;
  border-radius: 4px 0 0 4px;
}

.todo-form button {
  padding: 10px 20px;
  font-size: 16px;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 0 4px 4px 0;
  cursor: pointer;
}

.todo-filters {
  display: flex;
  justify-content: center;
  margin-bottom: 20px;
}

.todo-filters button {
  margin: 0 5px;
  padding: 5px 10px;
  font-size: 14px;
  background-color: #f1f1f1;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.todo-filters button.active {
  background-color: #4CAF50;
  color: white;
}

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

.todo-item {
  display: flex;
  align-items: center;
  padding: 10px;
  border-bottom: 1px solid #eee;
}

.todo-item input[type="checkbox"] {
  margin-right: 10px;
}

.todo-item span {
  flex-grow: 1;
}

.todo-item button {
  margin-left: 10px;
  padding: 5px 10px;
  font-size: 14px;
  background-color: #f44336;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.todo-stats {
  margin-top: 20px;
  text-align: center;
  color: #666;
}

그런 다음 TodoList.js 파일에서 이 CSS 파일을 import 합니다:

import React, { useState, useEffect } from 'react';
import './TodoList.css';

// ... 나머지 코드

2. 컴포넌트 분리

이제 TodoList 컴포넌트를 더 작은 컴포넌트로 분리하여 코드의 가독성과 재사용성을 높여보겠습니다.

TodoForm 컴포넌트

src/components 폴더에 TodoForm.js 파일을 생성하고 다음 내용을 추가합니다:

import React, { useState } from 'react';

function TodoForm({ onSubmit }) {
  const [inputValue, setInputValue] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (inputValue.trim() !== '') {
      onSubmit(inputValue);
      setInputValue('');
    }
  };

  return (
    <form className="todo-form" onSubmit={handleSubmit}>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        placeholder="새로운 할 일을 입력하세요"
      />
      <button type="submit">추가</button>
    </form>
  );
}

export default TodoForm;

TodoItem 컴포넌트

src/components 폴더에 TodoItem.js 파일을 생성하고 다음 내용을 추가합니다:

import React, { useState } from 'react';

function TodoItem({ todo, onToggle, onDelete, onEdit }) {
  const [isEditing, setIsEditing] = useState(false);
  const [editValue, setEditValue] = useState(todo.text);

  const handleEdit = () => {
    onEdit(todo.id, editValue);
    setIsEditing(false);
  };

  return (
    <li className="todo-item">
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      {isEditing ? (
        <input
          type="text"
          value={editValue}
          onChange={(e) => setEditValue(e.target.value)}
          onBlur={handleEdit}
          autoFocus
        />
      ) : (
        <span
          style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          onDoubleClick={() => setIsEditing(true)}
        >
          {todo.text}
        </span>
      )}
      <button onClick={() => onDelete(todo.id)}>삭제</button>
    </li>
  );
}

export default TodoItem;

TodoFilters 컴포넌트

src/components 폴더에 TodoFilters.js 파일을 생성하고 다음 내용을 추가합니다:

import React from 'react';

function TodoFilters({ filter, setFilter }) {
  return (
    <div className="todo-filters">
      <button
        className={filter === 'all' ? 'active' : ''}
        onClick={() => setFilter('all')}
      >
        전체
      </button>
      <button
        className={filter === 'active' ? 'active' : ''}
        onClick={() => setFilter('active')}
      >
        미완료
      </button>
      <button
        className={filter === 'completed' ? 'active' : ''}
        onClick={() => setFilter('completed')}
      >
        완료
      </button>
    </div>
  );
}

export default TodoFilters;

TodoStats 컴포넌트

src/components 폴더에 TodoStats.js 파일을 생성하고 다음 내용을 추가합니다:

import React from 'react';

function TodoStats({ todos }) {
  const totalTodos = todos.length;
  const completedTodos = todos.filter((todo) => todo.completed).length;
  const activeTodos = totalTodos - completedTodos;

  return (
    <div className="todo-stats">
      <p>전체 할 일: {totalTodos}</p>
      <p>완료한 할 일: {completedTodos}</p>
      <p>남은 할 일: {activeTodos}</p>
    </div>
  );
}

export default TodoStats;

3. 리팩토링된 TodoList 컴포넌트

이제 TodoList 컴포넌트를 수정하여 새로 만든 컴포넌트들을 사용하도록 합니다:

import React, { useState, useEffect } from 'react';
import TodoForm from './TodoForm';
import TodoItem from './TodoItem';
import TodoFilters from './TodoFilters';
import TodoStats from './TodoStats';
import './TodoList.css';

function TodoList() {
  const [todos, setTodos] = useState(() => {
    const savedTodos = localStorage.getItem('todos');
    return savedTodos ? JSON.parse(savedTodos) : [];
  });
  const [filter, setFilter] = useState('all');

  useEffect(() => {
    localStorage.setItem('todos', JSON.stringify(todos));
  }, [todos]);

  const addTodo = (text) => {
    setTodos([...todos, { id: Date.now(), text, completed: false }]);
  };

  const deleteTodo = (id) => {
    setTodos(todos.filter((todo) => todo.id !== id));
  };

  const toggleTodo = (id) => {
    setTodos(
      todos.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  const editTodo = (id, newText) => {
    setTodos(
      todos.map((todo) =>
        todo.id === id ? { ...todo, text: newText } : todo
      )
    );
  };

  const filteredTodos = todos.filter((todo) => {
    if (filter === 'active') return !todo.completed;
    if (filter === 'completed') return todo.completed;
    return true;
  });

  return (
    <div className="todo-app">
      <h2>할 일 관리 앱</h2>
      <TodoForm onSubmit={addTodo} />
      <TodoFilters filter={filter} setFilter={setFilter} />
      <ul className="todo-list">
        {filteredTodos.map((todo) => (
          <TodoItem
            key={todo.id}
            todo={todo}
            onToggle={toggleTodo}
            onDelete={deleteTodo}
            onEdit={editTodo}
          />
        ))}
      </ul>
      <TodoStats todos={todos} />
    </div>
  );
}

export default TodoList;

실행 결과

이제 npm start 명령어로 앱을 실행하면, 개선된 디자인과 함께 더 모듈화 된 구조의 앱을 볼 수 있습니다.

마무리

이번 챕터에서는 앱의 디자인을 개선하고 컴포넌트를 더 작은 단위로 분리하여 코드의 가독성과 유지보수성을 높였습니다. 이러한 리팩토링 과정은 앱이 커질수록 더욱 중요해집니다.

컴포넌트를 분리함으로써 얻을 수 있는 장점은 무엇일까요? 각 컴포넌트의 역할이 명확해지고, 재사용성이 높아지며, 테스트하기 쉬워진다는 점 등이 있습니다. 여러분은 어떤 장점을 느끼셨나요?

반응형