프로젝트 준비하기
요구사항 분석하기
리액트 앱 만들기
> npx create-react-app .
UI 구현하기
페이지 레이아웃 만들기
// App.js
import "./App.js"
function App() {
return (
<div className="App">
<Header />
<TodoEditor />
<TodoList />
</div>
);
}
export default App;
// component/Header.js
import "./Header.css";
const Header = () => {
return (
<div className="Header">
<h3>오늘은 📅</h3>
<h1>{new Date().toDateString()}</h1>
</div>
);
}
export default Header;
TodoEditor 컴포넌트 만들기
// component/TodoEditor.js
import "./TodoEditor.css"
const TodoEditor = () => {
return (
<div className="TodoEditor">
<h4>새로운 Todo 작성하기 ✏️</h4>
<div className="editor_wrapper">
<input placeholder="새로운 Todo..." />
<button>추가</button>
</div>
</div>
);
}
export default TodoEditor;
TodoList, TodoItem 컴포넌트 만들기
TodoList 컴포넌트 만들기
// component/TodoList.js
import "./TodoList.css";
const TodoLsit = () => {
return (
<div className="TodoList">
<h4>todo List 🌱</h4>
<input className="searchbar" placeholder="검색어를 입력하세요" />
</div>
);
}
export default TodoList;
TodoItem 컴포넌트 만들기
// component/TodoItem.js
import "./TodoItem.css";
const TodoItem = () => {
return (
<div className="TodoItem">
<div className="check_col">
<input type="checkbox" />
</div>
<div className="title_col">할 일</div>
<div className="date_col">{new Date().toLocaleDateString()}</div>
<div className="btn_col">
<button>삭제</button>
</div>
</div>
);
}
export default TodoItem;
TodoList에 TodoItem 컴포넌트 배치하기
// component/TodoList.js
import "./TodoList.css";
const TodoLsit = () => {
return (
<div className="TodoList">
<h4>todo List 🌱</h4>
<input className="searchbar" placeholder="검색어를 입력하세요" />
<div className="list_wrapper">
<TodoItem />
<TodoItem />
<TodoItem />
</div>
</div>
);
}
export default TodoList;
기능 구현 준비하기
기초 데이터 설정하기
State 변수 todo는 [할 일 관리] 앱에서 데이터를 저장하는 배열이면서 동시에 일종의 데이터베이스 역할을 수행함
// App.js
import { useState } from "react";
(...)
function App() {
const [todo, setTodo] = useState([]);
return (
(...)
);
}
export default App;
데이터 모델링하기
현실의 사물이나 개념을 프로그래밍 언어의 객체와 같은 자료구조로 표현하는 행위를 '데이터 모델링'이라고 함
{
id: 0,
isDone: false,
content: "React Study",
createdDate: new Date().getTime(),
}
목 데이터 설정하기
목(Mock) 데이터란 모조품 데이터라는 뜻으로, 기능을 완벽히 구현하지 않은 상태에서 테스트를 목적으로 사용하는 데이터
// App.js
import { useState } from "react";
(...)
const mockTodo = [
{
id: 0,
isDone: false,
content: "React Study",
createdDate: new Date().getTime(),
},
{
id: 1,
isDone: false,
content: "Laundry",
createdDate: new Date().getTime(),
},
{
id: 2,
isDone: false,
content: "Sing a Song",
createdDate: new Date().getTime(),
},
];
function App() {
const [todo, setTodo] = useState(mockTodo);
return (
(...)
);
}
export default App;
Create: 할 일 추가하기
기능 흐름 살펴보기
1. 사용자가 새로운 할 일 입력
2. TodoEditor 컴포넌트에 있는 <추가> 버튼 클릭
3. TodoEditor 컴포넌트는 부모인 App에게 아이템 추가 이벤트가 발생했음을 알리고 사용자가 추가한 할 일 데이터를 전달
4. App 컴포넌트는 TodoEditor 컴포넌트에서 받은 데이터를 이용해 새 아이템이 추가된 배열을 만들고 State 변수 todo 값을 업데이트 함
5. TodoEditor 컴포넌트는 자연스러운 사용자 경험을 위해 할 일 입력 폼을 초기화 함
아이템 추가 함수 만들기
App 컴포넌트에서 새 할 일 아이템을 추가하는 함수 onCreate를 만들되, idRef를 이용해 아이템을 생성할 때마다 id가 1씩 늘어나도록 하고, 사용자가 TodoEditor 컴포넌트에서 <추가> 버튼을 클릭해야 호출되기 때문에 TodoEditor 컴포넌트에 onCreate 함수를 Props로 전달해야 함
// App.js
(...)
function App() {
const [todo, setTodo] = useState(mockTodo);
const idRef = useRef(3);
const onCreate = (content) => {
const newItem = {
id: idRef.current,
content,
isDone: false,
createdDate: new Date().getTime(),
};
setTodo([newItem, ...todo]);
idRef += 1;
};
return (
<div className="App">
<Header />
<TodoEditor onCreate={onCreate} />
<TodoList />
</div>
);
}
export default App;
! TodoEditor에서 Props를 받을 때는 Props 객체를 구조분해 할당 함
아이템 추가 함수 호출하기
TodoEditor 컴포넌트의 할 일 입력 폼에서 사용자가 입력하는 새 할 일 데이터를 저장할 State와 onChangeContent 함수를 만들고, <추가> 버튼을 클릭하면 함수 onCreate를 호출하는 버튼 클릭 이벤트 핸들러 함수 onSubmit을 만듦
// component/TodoEditor.js
(...)
const TodoEditor = ({ onCreate }) => {
const [contet, setContent] = useState("");
const onChangeContent = (e) => {
setContent(e.target.value);
};
const onSubmit = () => {
onCreate(content);
};
return (
<div className="TodoEditor">
<div className="editor_wrapper">
<input value={content} onChange={onChangeContent} placeholder="new Todo..." />
<button onClick={onSubmit}>추가</button>
</div>
</div>
);
};
export default TodoEditor;
Create 완성도 높이기
빈 입력 방지하기
아무것도 입력하지 않은 상태로 아이템을 추가하는 것을 방지하기 위한 방법으로, 함수 onSubmit에서 content 값이 비어 있으면 입력 폼에 포커스를 구현함
// component/TodoEditor.js
(...)
const TodoEditor = ({ onCreate }) => {
(...)
const inputRef = useRef();
const onSubmit = () => {
if (!content) {
inputRef.current.focus();
return;
}
onCreate(content);
};
(...)
};
export default TodoEditor;
아이템 추가 후 입력 폼 초기화하기
아이템을 추가하면 자동으로 할 일 입력 폼을 초기화하는 기능 구현
// component/TodoEditor.js
(...)
const TodoEditor = ({ onCreate }) => {
(...)
const inputRef = useRef();
const onSubmit = () => {
(...)
setContent("");
};
(...)
};
export default TodoEditor;
Enter 키를 눌러 아이템 추가하기
Enter 키를 누르면, <추가> 버튼을 클릭핳ㄴ 것과 동일한 동작을 수행하는 키 입력 이벤트 구현
// component/TodoEditor.js
(...)
const TodoEditor = ({ onCreate }) => {
(...)
const onKeyDown = (e) => {
if (e.keyCode === 13) {
onSubmit();
}
};
return (
<div className="TodoEditor">
<div className="editor_wrapper">
<input value={content} onChange={onChangeContent} onKeyDown={onKeyDown} placeholder="new Todo..." />
<button onClick={onSubmit}>추가</button>
</div>
</div>
);
(...)
};
export default TodoEditor;
Read: 할 일 리스트 렌더링하기
배열을 리스트로 렌더링하기
App 컴포넌트에서는 todo를 TodoList 컴포넌트에 Props로 전달하고, TodoList 컴포넌트에서는 Props로 전달된 todo를 리스트로 렌더링 해야 함
// App.js
(...)
function App() {
const [todo, setTodo] = setState(mockTodo);
(...)
return (
(...)
<TodoList todo={todo} />
);
}
export default App;
map을 이용한 HTML 반복하기
배열 메서드 map을 이용해 HTML 요소를 반복해서 렌더링 함
// component/TodoList.js
(...)
const TodoList = ({ todo }) => {
return (
(...)
<div className="list_wrapper">
{todo.map((it) => (
<div>{it.content}</div>
))}
</div>
);
};
export default TodoList;
(it) => {} 가 아니라 왜 (it) => () 인거지?
map을 이용해 컴포넌트 반복하기
map 메서드의 콜백 함수가 HTML이 아닌 컴포넌트를 반환하도록 수정
// component/TodoList.js
(...)
const TodoList = ({ todo }) => {
return (
(...)
<div className="list_wrapper">
{todo.map((it) => (
<TodoItem {...it} />
))}
</div>
);
};
export default TodoList;
it={it} 가 아닌 {... it} 로 사용된 이유?
TodoItem 컴포넌트에 전달된 Props를 이 컴포넌트에서 사용할 수 있도록 수정
// component/TodoItem.js
import "./TodoItem.css";
const TodoItem = ({ id, content, isDone, createdDate }) => {
return (
<div className="TodoItem">
<div className="check_col">
<input checked={isDone} type="checkbox" />
</div>
<div className="title_col">{content}</div>
<div className="date_col">{new Date().toLocaleDateString()}</div>
<div className="btn_col">
<button>삭제</button>
</div>
</div>
);
}
export default TodoItem;
key 설정하기
map을 이용해 컴포넌트를 리스트 형태로 반복적으로 렌더링하려면 반드시 리스트 내의 고유한 key를 Props로 전달해야 함
// component/TodoList.js
(...)
const TodoList = ({ todo }) => {
return (
(...)
<div className="list_wrapper">
{todo.map((it) => (
<TodoItem key={it.id} {...it} />
))}
</div>
);
};
export default TodoList;
검색어에 따라 필터링하기
검색 기능 만들기
사용자가 입력하는 검색어를 처리할 State 변수를 만든 다음, 검색 폼에서 사용자가 입력한 내용을 처리하는 기능 구현
// component/TodoList.js
(...)
const TodoList = ({ todo }) => {
const [search, setSearch] = useState("");
const onChangeSearch = (e) => {
setSearch(e.target.value);
};
return (
(...)
<input value={search} onChange={onChangeSearch}
className="searchBar" placeholder="검색어를 입력하세요" />
(...)
);
};
export default TodoList;
사용자가 입력한 검색어에 따라 할 일 아이템을 필터링하는 기능 구현
// component/TodoList.js
(...)
const TodoList = ({ todo }) => {
(...)
const getSearchResult = () => {
return search === "" ? todo :
todo.filter((it) => it.content.includes(search));
};
return (
(...)
<div className="list_wrapper">
{getSearchResult().map((it) => (
<TodoItem key={it.id} {...it} />
))}
</div>
);
};
export default TodoList;
대소 문자를 구별하지 않게 하기
검색에서 대소 문자를 구별하지 않도록 하는 기능 구현
// component/TodoList.js
(...)
const TodoList = ({ todo }) => {
(...)
const getSearchResult = () => {
return search === "" ? todo :
todo.filter((it) =>
it.content.toLowerCase().includes(search.toLowerCase()));
};
(...)
};
export default TodoList;
Update: 할 일 수정하기
기능 흐름 살펴보기

아이템 수정 함수 만들기
할 일 수정 함수 onUpdate를 생성하고 TodoList 컴포넌트에 Props로 전달
// App.js
(...)
function App() {
const onUpdate = (targetId) => {
setTodo(
todo.map((it) =>
it.id === targetId ? { ...it, isDone: !it.isDone } : it
)
);
};
return (
<div className="App">
<Header />
<TodoEditor onCreate={onCreate} />
<TodoList todo={todo} onUpdate={onUpdate} />
</div>
);
}
export default App;
TodoList에서 TodoItem 컴포넌트에 함수 onUpdate를 전달
// component/TodoList.js
(...)
const TodoList = ({ todo, onUpdate }) => {
(...)
return (
(...)
<div className="list_wrapper">
{getSearchResult().map((it) => (
<TodoItem key={it.id} {...it} onUpdate={onUpdate} />
))}
</div>
);
};
export default TodoList;
TodoItem 컴포넌트에서 아이템 수정 함수 호출하기
TodoItem 컴포넌트에서 틱 이벤트가 발생하면 함수 onUpdate를 호출
// component/TodoItem.js
import "./TodoItem.css";
const TodoItem = ({ id, content, isDone, createdDate, onUpdate }) => {
const onChangeCheckbox = () => {
onUpdate(id);
};
return (
<div className="TodoItem">
<div className="check_col">
<input onChange={onChangeCheckbox} checked={isDone} type="checkbox" />
</div>
(...)
</div>
);
}
export default TodoItem;
Delete: 할 일 삭제하기
기능 흐름 살펴보기
수정 기능과 유사한 흐름으로 진행
아이템 삭제 함수 만들기
App 컴포넌트에서 할 일을 삭제하는 함수 onDelete를 작성
// App.js
(...)
function App() {
const onDelete = (targetId) => {
setTodo(todo.filter((it) => it.id !== targetId));
};
return (
<div className="App">
<Header />
<TodoEditor onCreate={onCreate} />
<TodoList todo={todo} onUpdate={onUpdate} onDelete={onDelete} />
</div>
);
}
export default App;
// component/TodoList.js
(...)
const TodoList = ({ todo, onUpdate, onDelete }) => {
(...)
return (
(...)
<div className="list_wrapper">
{getSearchResult().map((it) => (
<TodoItem key={it.id} {...it} onUpdate={onUpdate} onDelete={onDelete} />
))}
</div>
);
};
export default TodoList;
TodoItem 컴포넌트에서 삭제 함수 호출하기
// component/TodoItem.js
import "./TodoItem.css";
const TodoItem = ({ id, content, isDone, createdDate, onUpdate, onDelete }) => {
(...)
const onClickDelete = () => {
onDelete(id);
};
return (
<div className="TodoItem">
(...)
<div className="btn_col">
<button onClick={onClickDelete}>삭제</button>
</div>
</div>
);
}
export default TodoItem;