티스토리 뷰
이제까지 리액트의 상태나 컴포넌트 등 기초적인 부분을 다뤄봤으니, 국민 첫 프로젝트인 Todo List를 한번 만들어보려고 한다.
SPA(single page application)으로 만들기 때문에 그렇게 많은 컴포넌트나 코드는 없이, 정말 간단한 CRUD 기능을 가지고 있는 Todo List 이다.
바로 한번 시작해보자.
우선, 총 App 컴포넌트를 제외하고 총 4개의 컴포넌트를 만들어 사용할 것인데, 각 컴포넌트를 용도별로 확인해보자.
1. TodoTemplate: 화면을 가운데에 정렬시켜 주며, 앱 타이틀을 보여준다.
2. TodoInsert: 새로운 항목을 입력하고 추가할 수 있는 컴포넌트이다. state를 통해 인풋의 상태를 관리한다.
3. TodoListItem: 각 To do 항목에 대한 정보를 보여주는 컴포넌트이다. todo 객체를 props로 받아 와서 상태에 따라 다른 스타일의 UI를 보여준다.
4. TodoList: todos 배열을 props로 받아 온 후, 이를 배열 내장 함수 map을 사용해서 여러 개의 TodoListItem 컴포넌트로 변환하여 보여준다.
추가로, 이번 프로젝트에서 사용한 아이콘은 react-icons 라이브러리를 사용했다.
react-icons는 여기에서 확인할 수 있으며, 이 라이브러리는 SVG 형태로 이루어진 아이콘을 리액트 컴포넌트처럼 매우 쉽게 사용할 수 있다.
TodoTemplate
TodoTemplate 컴포넌트는 children props를 받아서 내부에 셋팅해놓는, Wrapper의 역할을 하고 있다고 보면 된다.
◎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;
◎TodoTemplate.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;
}
}
TodoInsert
TodoInsert 컴포넌트는 input 창에 할 일을 적고 추가 버튼을 눌러 리스트에 아이템을 추가할 컴포넌트이다.
대부분의 이벤트는 App 컴포넌트에서 선언하고, props로 사용할 컴포넌트에 보내는 방식이다.
◎TodoInsert.js
import {MdAdd} from 'react-icons/md';
import {useState, useCallback} from "react";
import './TodoInsert.scss';
const TodoInsert = ({onInsert}) => {
const [value, setValue] = useState('');
const onChange = useCallback(e => {
setValue(e.target.value);
}, []);
const onSubmit = useCallback(
e => {
onInsert(value);
setValue(''); // value 값 초기화
// submit 이벤트는 브라우저에서 새로고침을 발생시키기 때문에, 호출
e.preventDefault();
}, [onInsert, value]
)
return (
<form className="TodoInsert" onSubmit={onSubmit}>
<input
placeholder="할 일을 입력하세요."
value={value}
onChange={onChange}
/>
<button type="submit">
<MdAdd/>
</button>
</form>
);
};
export default TodoInsert;
◎TodoInsert.scss
.TodoInsert {
display: flex;
background: #495057;
input {
outline: none;
border: none;
padding: 0.5rem;
font-size: 1.125rem;
line-height: 1.5;
color: black;
&::placeholder {
color: #dee2e6;
}
flex: 1;
}
button {
background: none;
outline: none;
border: none;
background: #868e96;
color: white;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1.5rem;
display: flex;
align-items: center;
cursor: pointer;
transition: 0.1s background ease-in;
&:hover {
background: #adb5bd;
}
}
}
TodoList
TodoList 컴포넌트는 상대적으로 매우 간단하다. 그냥 todos 를 props로 받아 map 함수로 반복문을 돌려 TodoListItem 컴포넌트에 각 todo를 넣어주는 역할을 한다.
◎TodoList.js
import TodoListItem from './TodoListItem';
import './TodoList.scss';
const TodoList = ({todos, onRemove, onToggle}) => {
return (
<div className="TodoList">
{todos.map(todo => (
<TodoListItem todo={todo} key={todo.id} onRemove={onRemove} onToggle={onToggle}/>
))}
</div>
);
};
export default TodoList;
◎TodoList.scss
.TodoList {
min-height: 320px;
max-height: 513px;
overflow-y: auto;
}
TodoListItem
TodoListItem 컴포넌트는 todo props을 받아서 화면에 뿌려주고, 각 아이콘 버튼 아이콘에 클릭 이벤트를 걸어 이벤트를 핸들링할 수 있게 해준다.
◎TodoListItem.js
import {
MdCheckBoxOutlineBlank,
MdCheckBox,
MdRemoveCircleOutline,
} from "react-icons/md";
import './TodoListItem.scss';
import cn from 'classnames';
const TodoListItem = ({todo, onRemove, onToggle}) => {
const {id, text, checked} = todo;
return(
<div className="TodoListItem">
<div className={cn('checkbox', {checked})} onClick={() => onToggle(id)}>
{checked ? <MdCheckBox/> : <MdCheckBoxOutlineBlank/>}
<div className="text">{text}</div>
</div>
<div className="remove" onClick={() => onRemove(id)}>
<MdRemoveCircleOutline/>
</div>
</div>
);
};
export default TodoListItem;
◎TodoListItem.scss
.TodoListItem {
padding: 1rem;
display: flex;
align-items: center;
&:nth-child(even) {
background: #f8f9fa;
}
.checkbox {
cursor: pointer;
flex: 1;
display: flex;
align-items: center;
svg {
font-size: 1.5rem;
}
.text{
margin-left: 0.5rem;
flex: 1;
}
// 체크되었을 때
&.checked {
svg {
color: #22b8cf;
}
.text {
color: #adb5bd;
text-decoration: line-through;
}
}
}
.remove {
display: flex;
align-items: center;
font-size: 1.5rem;
color: #ff6b6b;
cursor: pointer;
&:hover {
color: #ff8787;
}
}
// 엘리먼트 사이사이에 테두리를 넣어줌
& + & {
border-top: 1px solid #dee2e6;
}
}
이제, 마지막으로 App 컴포넌트에 추가, 수정, 삭제 기능을 구현할 함수를 useCallback을 사용하여 선언해주고, TodoTemplate컴포넌트 내부에 TodoInsert와 TodoList를 넣어주기만 하면 끝난다.
◎App.js
import './App.css';
import TodoTemplate from "./components/TodoTemplate";
import TodoInsert from "./components/TodoInsert";
import TodoList from "./components/TodoList";
import {useState, useRef, useCallback} from "react";
const App = () => {
const [todos, setTodos] = useState([
{
id: 1,
text: '리액트의 기초 알아보기',
checked: true
},
{
id: 2,
text: '컴포넌트 스타일링 해보기',
checked: true
},
{
id: 3,
text: '일정 관리 앱 만들어보기',
checked: false
}
]);
// ref 를 사용하여 변수 담기
const nextId = useRef(4);
const onInsert = useCallback(
text => {
const todo = {
id: nextId.current,
text,
checked: false
};
setTodos(todos.concat(todo));
nextId.current += 1;
}, [todos]
);
const onRemove = useCallback(
id => {
setTodos(todos.filter(todo => todo.id !== id));
}, [todos]
);
const onToggle = useCallback(
id => {
setTodos(
todos.map(todo =>
todo.id === id ? {...todo, checked: !todo.checked } : todo
)
);
}, [todos]
);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert}/>
<TodoList todos={todos} onRemove={onRemove} onToggle={onToggle}/>
</TodoTemplate>
)
};
export default App;
모두 이전에 다뤘던 개념들이고 코드를 차근차근 데이터를 따라 확인해보면 크게 어려울 내용은 없기 때문에 일부러 따로 설명을 많이 적어두지는 않았다.
이렇게 리액트 공부를 시작하고 처음으로 하나의 프로젝트를 만들어 보았다.
하지만 이 프로젝트는 props를 가지고 다니면서 전달하기 때문에 todos의 크기가 커진다면 충분히 성능에 문제가 생길 것이 분명하다.
이를 위해 다음 포스팅에서는 이 TodoList 프로젝트를 최적화하는 방법을 다뤄보려고 한다.
끝!
'WEB > React' 카테고리의 다른 글
[React] 불변성 유지: immer (0) | 2023.03.29 |
---|---|
[React] Todo List: 컴포넌트 성능 최적화 (0) | 2023.03.29 |
[React] 컴포넌트 스타일링 (0) | 2023.03.27 |
[React] Hooks (0) | 2023.03.27 |
[React] 컴포넌트의 라이프사이클 (0) | 2023.03.24 |
- 정보보안기사 #실기 #정리
- redux
- 인천 구월동 맛집
- javascript
- 인천 구월동 이탈리안 맛집
- Async
- react-native
- react
- await
- Promise
- 맛집
- 이탈리안 레스토랑
- 파니노구스토
- AsyncStorage
- redux-thunk
- Total
- Today
- Yesterday