리팩토링 일명 '파일 세분화하기'를 해보았다.
일단 기존 파일은 src/component/TodoList.jsx 하나에 App.jsx의 file tree.
이제 component 폴더 아래
TodoContainer - 상태를 중앙 집중 관리
TodoForm - 제출 받는 폼
TodoList - 리스트를 렌더링
TodoItem - 각 아이템 하나를 렌더링
이런 파일 4개를 세분화 refactoring하겠다.
→ 일단 기존 코드에서 간결하고 직관적이게 최적화 작업을 거쳤다.
import { SAMPLE_TODOS } from "../../constants/sample-todos";
import React, { useState } from "react";
const TodoContainer = () => {
const [todos, setTodos] = useState(SAMPLE_TODOS);
const [newTodo, setNewTodo] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (!newTodo.trim()) {
return;
}
const newtodoObj = {
id: crypto.randomUUID(),
text: newTodo,
completed: false,
};
setTodos([newtodoObj, ...todos]);
setNewTodo("");
};
const handleInputChange = (e) => {
setNewTodo(e.target.value);
};
const toggleCompleted = (id) =>
setTodos((prevTodos) =>
prevTodos.map((todo) =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
);
const handleDelete = (id) =>
setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={newTodo}
onChange={handleInputChange}
placeholder="Enter a new todo"
/>
<button type="submit">Add Todo</button>
</form>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => handleUpdate(todo.id)}
/>
{todo.text}
{todo.completed ? <p>완료됨</p> : <p>진행중</p>}
<button onClick={() => handleDelete(todo.id)}>삭제</button>
</li>
))}
</ul>
</div>
);
};
export default TodoContainer;
handleSubmit 함수에서 newtodoObj 추가 방법 변경
→ 이전 코드:
setTodos([
...todos,
{ id: crypto.randomUUID(), text: newTodo, completed: false },
]);
→ 변경된 코드:
const newtodoObj = {
id: crypto.randomUUID(),
text: newTodo,
completed: false,
};
setTodos([newtodoObj, ...todos]);
newtodoObj를 먼저 정의한 후, setTodos에서 이를 사용해 todos 배열 앞에 새 항목을 추가했습니다. 이전 코드에서는 새로 추가된 할 일이 배열의 마지막에 위치했지만, 변경된 코드에서는 새 할 일이 배열의 맨 앞에 위치합니다. 이로 인해 할 일이 추가될 때마다 최신 항목이 리스트의 맨 위에 나타납니다.
toggleCompleted 함수의 변경
→ 이전 코드:
const handleUpdate = (id) => {
const updatedTodos = todos.map((todo) => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed,
};
} else {
return todo;
}
});
setTodos(updatedTodos);
};
→ 변경된 코드:
const toggleCompleted = (id) =>
setTodos((prevTodos) =>
prevTodos.map((todo) =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
);
함수 이름이 handleUpdate에서 toggleCompleted로 변경되었습니다.
setTodos 함수 내에서 이전 상태(prevTodos)를 사용하여 map 함수를 통해 해당 id의 completed 상태를 반전시키도록 했습니다.
handleDelete 함수의 단축
→ 이전 코드:
const handleDelete = (id) => {
const updatedTodos = todos.filter((todo) => todo.id !== id);
setTodos(updatedTodos);
};
→ 변경된 코드:
const handleDelete = (id) =>
setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
handleDelete 함수가 더 간결하게 작성되었습니다. filter 메소드를 사용해 조건에 맞지 않는 요소를 제거한 후, 바로 setTodos를 통해 상태를 업데이트합니다. 이전 상태(prevTodos)를 사용하여 동일한 작업을 수행합니다.
함수 호출 이름 변경 (handleUpdate -> toggleCompleted)
→ 이전 코드:
onChange={() => handleUpdate(todo.id)}
→ 변경된 코드:
onChange={() => toggleCompleted(todo.id)}
handleUpdate 함수 호출이 toggleCompleted로 변경되었습니다. 이로 인해 할 일의 completed 상태를 반전시키는 함수 이름이 더 직관적으로 변경되었습니다.
이제 이 코드를 4개의 파일로 리팩토링 할 것이다.
import React, { useState } from "react";
const TodoForm = ({ addTodos }) => {
const [newTodo, setNewTodo] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (!newTodo.trim()) {
return;
}
const newtodoObj = {
id: crypto.randomUUID(),
text: newTodo,
completed: false,
};
// setTodos([newtodoObj, ...todos]);
addTodos(newtodoObj);
setNewTodo("");
};
const handleInputChange = (e) => {
setNewTodo(e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={newTodo}
onChange={handleInputChange}
placeholder="Enter a new todo"
/>
<button type="submit">Add Todo</button>
</form>
);
};
export default TodoForm;
setTodos([newtodoObj, ...todos]);→ addTodos(newtodoObj);
TodoForm 컴포넌트는 addTodos라는 새로운 함수를 props로 받아옵니다. TodoForm은 더 이상 todos나 setTodos를 필요로 하지 않으며, addTodos 함수만을 호출. 기존의 setTodos가 정의되어 있지 않아 새로 선언하고 TodoForm이 받아옴.
❓ 리팩토링의 주요 목표는 단일 책임 원칙(SRP)을 따르는 것입니다. 즉, TodoForm 컴포넌트는 할 일을 추가하는 입력 폼의 역할만 담당하며, 상태 업데이트는 부모 컴포넌트가 관리하도록 변경되었습니다. 이렇게 하면 코드의 재사용성과 유지보수성이 향상됩니다.
const TodoItem = ({ todo, toggleCompleted, handleDelete }) => {
return (
<li key={todo.id}>
<p
style={{
textDecoration: todo.completed ? "line-through" : "none",
}}
>
{todo.text} -{" "}
{todo.completed ? <span>완료됨</span> : <span>진행중</span>}
</p>
<button onClick={() => toggleCompleted(todo.id)}>
{todo.completed ? "취소" : "왼료"}
</button>
<button onClick={() => handleDelete(todo.id)}>삭제</button>
</li>
);
};
export default TodoItem;
화면에 렌더링 될 TodoItem을 보여준다
import TodoItem from "./TodoItem";
const TodoList = ({ todos, toggleCompleted, handleDelete }) => {
return (
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
toggleCompleted={toggleCompleted}
handleDelete={handleDelete}
/>
))}
</ul>
);
};
export default TodoList;
TodoItem에서 정보를 받아와서 map함수를 통해 리스트로 뿌려주고 화면에 렌더링.
import { SAMPLE_TODOS } from "../../constants/sample-todos";
import React, { useState } from "react";
import TodoForm from "./TodoFOrm";
import TodoList from "./TodoList";
const TodoContainer = () => {
const [todos, setTodos] = useState(SAMPLE_TODOS);
const addTodos = (newTodoObj) => setTodos([newTodoObj, ...todos]);
const toggleCompleted = (id) =>
setTodos((prevTodos) =>
prevTodos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
// 1. Todo 항목을 삭제하는 함수 정의
const handleDelete = (id) =>
// 2. 선택된 항목을 제외한 새로운 배열 생성
setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
return (
<div>
<TodoForm addTodos={addTodos} />
<TodoList
todos={todos}
toggleCompleted={toggleCompleted}
handleDelete={handleDelete}
/>
</div>
);
};
export default TodoContainer;
addTodos 함수를 TodoContainer 컴포넌트에서 선언하고, 이를 TodoForm 컴포넌트에 prop으로 전달하여, TodoForm에서 이 함수를 호출함으로써 새로운 할 일 객체를 부모 컴포넌트(TodoContainer)로 전달하는 방식.
const handleDelete = (id) =>
setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
prevTodos.filter: todos 배열에서 주어진 id를 제외한 새로운 배열을 생성합니다.
const toggleCompleted = (id) =>
setTodos((prevTodos) =>
prevTodos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
toggleCompleted: 이 함수는 특정 id를 가진 할 일의 완료 상태를 토글(반전)합니다.
setTodos: 상태를 업데이트하는 함수로, 이전 상태 prevTodos를 받아서 새로운 배열을 반환합니다.
prevTodos.map: todos 배열의 각 항목을 순회하면서, 주어진 id와 일치하는 항목의 completed 상태를 반전시킵니다.
삼항 연산자: todo.id === id가 참이면 completed 상태를 반전시키고, 그렇지 않으면 기존의 todo 객체를 그대로 반환합니다.