[React] React Project 1 - ToDo List
2020. 4.10
React Project 1 - ToDo List
Intro
React를 이용해 간단한 Todo-List 페이지를 만들어 보았다.
결과 화면은 다음과 같다.

참고 : Velopert님의 React 기초 입문 프로젝트 – 흔하디 흔한 할 일 목록 만들기
필요한 컴포넌트
- TodoListTemplate : 전체 템플릿을 담고 있는 컴포넌트
- Form : input과 추가 버튼을 관리하는 컴포넌트
- TodoItem : 하단의 각 todo 아이템을 관리하는 컴포넌트
- TodoItemList : 여러 개의 TodoItem을 관리하는 컴포넌트
- Palette : 글자 색을 지정할 수 있는 컴포넌트
상태 관리 방법
<컴포넌트 전체 구조>

하위 컴포넌트들끼리 직접 데이터를 주고받는 방식은 규모가 커졌을 때 유지보수 하기가 힘들다.
따라서, 모든 컴포넌트들은 부모를 통하여 데이터를 주고받아야 한다.
즉, 부모 컴포넌트에서 자식 컴포넌트에 state값과 함수들을 전달하면, 자식 컴포넌트에서는 props로 받아와 기능을 수행할 수 있다. (전달할 함수의 기능은 부모 컴포넌트에서 구현한다. )
App에서 정의할 변수
-
state에서 관리하는 변수
- input : Form의 input 상태를 변경하고, 제출한 경우 clear해야 하는 변수
- todos {id, text, checked} : 각 할일 목록이 담긴 배열. 고유 id와 text, 체크 여부를 결정하는 checked 변수가 포함된다.
- color : Palette에서 특정 color가 선택될 때마다 바뀌어야 하는 변수.
-
일반 변수
- colors : 각 색깔이 담긴 배열. 값이 바뀔 필요가 없으므로 state에서 관리하지 않는다.
Form 컴포넌트에서 필요한 기능
- 텍스트 내용 바뀌면 state 업데이트 :
handleChange(e) - 버튼이 클릭되면 새로운 todo 생성 후 todos 업데이트 :
handleCreate()
⇒ concat() 함수를 이용하여 원래 배열의 데이터들이 포함된 새로운 배열을 생성한다.
- 인풋에서 Enter 누르면 버튼을 클릭한것과 동일한 작업진행하기 :
handleKeyPress(e)
Palette 컴포넌트에서 필요한 기능
- 특정 색이 클릭되면 state의 color값 업데이트 :
handleSelectColor(color)
TodoItemList 컴포넌트에서 필요한 기능
-
아이템이 클릭될 때마다 상태값 반전 (toggle 기능) :
handleToggle(id)⇒ 선택된 id를 가진 아이템의 index와 해당 객체를 추출하고 (
selected),const nextTodos = [...todos]를 이용하여 원래 배열의 데이터가 담긴 새로운 배열(nextTodos)을 생성한다.선택된 객체(
selected)의 나머지 데이터는 그대로 둔 채checked값만 반전시킨다. -
아이템 제거 기능 :
handleRemove(id)⇒
filter(todo=> [todo.id](http://todo.id/)!==id)이용.
전체 Code
App.js
import React, { Component } from "react";
import TodoListTemplate from "./components/TodoListTemplate";
import Form from "./components/Form";
import TodoItemList from "./components/TodoItemList";
import Palette from "./components/Palette";
/* TODO: Form 컴포넌트에서 필요한 기능
1. 텍스트 내용 바뀌면 state 업데이트
2. 버튼이 클릭되면 새로운 todo 생성 후 todos 업데이트
3. 인풋에서 Enter 누르면 버튼을 클릭한것과 동일한 작업진행하기 */
const colors = ["#343a40", "#f03e3e", "#12b886", "#228ae6"];
class App extends Component {
id = 3;
state = {
input: "",
todos: [
{ id: 0, text: "hi", checked: false },
{ id: 1, text: "react", checked: true },
{ id: 2, text: "welcome", checked: false }
],
color: "#343a40"
};
handleChange = e => {
this.setState({
input: e.target.value
});
};
handleCreate = () => {
const { input, todos, color } = this.state;
this.setState({
input: "",
todos: todos.concat({
id: this.id++,
text: input,
checked: false,
color
})
});
};
handleKeyPress = e => {
if (e.key === "Enter") {
this.handleCreate();
}
};
handleToggle = id => {
const { todos } = this.state;
const index = todos.findIndex(todo => todo.id === id);
const selected = todos[index];
const nextTodos = [...todos];
nextTodos[index] = {
...selected,
checked: !selected.checked
};
this.setState({
todos: nextTodos
});
};
handleRemove = id => {
const { todos } = this.state;
this.setState({
todos: todos.filter(todo => todo.id !== id)
});
};
handleSelectColor = color => {
this.setState({
color
});
};
render() {
const { input, todos, color } = this.state;
const {
handleChange,
handleCreate,
handleKeyPress,
handleToggle,
handleRemove,
handleSelectColor
} = this;
return (
<TodoListTemplate
form={
<Form
value={input}
color={color}
onKeyPress={handleKeyPress}
onChange={handleChange}
onCreate={handleCreate}
/>
}
palette={
<Palette
colors={colors}
selected={color}
onSelect={handleSelectColor}
/>
}
>
<TodoItemList
todos={todos}
onToggle={handleToggle}
onRemove={handleRemove}
/>
</TodoListTemplate>
);
}
}
export default App;TodoListTemplate.js
import React from "react";
import "./TodoListTemplate.css";
const TodoListTemplate = ({ form, palette, children }) => {
return (
<main className="todo-list-template">
<div className="title">오늘 할 일</div>
<section className="palette-wrapper">{palette}</section>
<section className="form-wrapper">{form}</section>
<section className="todos-wrapper">{children}</section>
</main>
);
};
export default TodoListTemplate;Form.js
import React from "react";
import "./Form.css";
const Form = ({ value, color, onChange, onCreate, onKeyPress }) => {
return (
<div className="form">
<input
value={value}
onChange={onChange}
onKeyPress={onKeyPress}
style={{ color }}
/>
<div className="create-button" onClick={onCreate}>
추가
</div>
</div>
);
};
export default Form;Palette.js
import React from "react";
import "./Palette.css";
const Color = ({ color, active, onClick }) => {
/* 구현 */
return (
<div
className={`color ${active && "active"}`}
style={{ background: color }}
onClick={onClick}
></div>
);
};
const Palette = ({ colors, selected, onSelect }) => {
const colorList = colors.map(color => (
<Color
color={color}
active={selected === color}
onClick={() => onSelect(color)}
key={color}
/>
));
return <div className="palette">{colorList}</div>;
};
export default Palette;TodoItemList.js
import React, { Component } from "react";
import TodoItem from "./TodoItem";
import "./TodoItemList.css";
class TodoItemList extends Component {
shouldComponentUpdate(nextProps, nextState) {
return this.props.todos !== nextProps.todos;
}
render() {
const { todos, onToggle, onRemove } = this.props;
/* const todoList = todos.map(
({id,text,checked})=> (
<TodoItem
id={id}
text={text}
checked={checked}
onToggle={onToggle}
onRemove={onRemove}
key={id}
/>
)
) */
const todoList = todos.map(todo => (
<TodoItem
{...todo}
// 내부의 값들이 모두 자동으로 props로 설정됨
onToggle={onToggle}
onRemove={onRemove}
key={todo.id}
/>
));
return <div>{todoList}</div>;
}
}
export default TodoItemList;TodoItem.js
import React, { Component } from "react";
import "./TodoItem.css";
class TodoItem extends Component {
shouldComponentUpdate(nextProps, nextState) {
return this.props.checked !== nextProps.checked;
}
render() {
const { text, checked, id, color, onToggle, onRemove } = this.props;
console.log(id);
return (
<div className="todo-item" onClick={() => onToggle(id)}>
<div
className="remove"
onClick={e => {
e.stopPropagation();
onRemove(id);
}}
>
×
</div>
<div
className={`todo-text ${checked ? "checked" : ""}`}
style={{ color: color }}
>
<div>{text}</div>
</div>
{checked && <div className="check-mark">✓</div>}
</div>
);
}
}
export default TodoItem;