들어가며
이번주 금요일(08/25)부터 3주간에 걸쳐 진행될
[Chapter 3] 주특기 주차가 시작되어 금요일부터 오늘까지
리액트 입문주차 강의를 듣고, Lv.1 과제를 수행했습니다.
Lv.1 과제의 내용은 다음과 같습니다.
과제 요구사항
React 를 이용한 Todo List 만들기
- UI 구현하기
- Todo 추가하기
- Todo 삭제하기
- Todo 완료 상태 변경하기(완료 ↔ 진행중)
과제를 수행하며
Lv.1 과제를 수행하면서 리액트에서 가장 중요한 개념인 props 와 state 의 동작하는 방법에 대해 직접 느껴볼 수 있었고,
컴포넌트와 리액트 훅(useState, useRef) 요소들을 다뤄보며 컴포넌트에 저장된 데이터가 변화하면 UI 가 자동으로 갱신되고, 컴포넌트 기반 아키텍처인 리액트에서 데이터는 부모자식 간의 컴포넌트에서 단방향 데이터 흐름을 갖는다는 점을 알았습니다.
또, 자바스크립트와 XML 을 조합한 JSX 문법을 사용하면서 '명령형' 프로그래밍보다 '선언적' 프로그래밍이 가독성을 높이고, 컴포넌트의 렌더링을 보다 직관적으로 이해할 수 있도록 도와준다는 리액트의 특징에 대해 배웠습니다.
https://github.com/yngjnhyk/react_todolist
GitHub - yngjnhyk/react_todolist
Contribute to yngjnhyk/react_todolist development by creating an account on GitHub.
github.com
react_todolist info
수행한 프로젝트의 기능들을 components 별로 설명해보겠습니다.
- Header : 페이지 상단(날짜표기)
- TodoEditor : 할 일 생성
- TodoList : 할 일 렌더링
- DoneList : 완료한 일 렌더링
- TodoItem : 할 일들
- DoneItem : 완료한 일들
총 6가지 컴포넌트를 생성해 리액트 프로젝트를 만들게 되었습니다.
1. Header : 페이지 상단
Header 컴포넌트는 페이지의 상단에서 오늘의 날짜를 표기합니다.
import React from 'react';
import './Header.css';
function Header() {
return (
<div className='Header'>
<h3>오늘은 📅</h3>
<h1>{new Date().toDateString()}</h1>
</div>
);
}
export default Header;
자바스크립트 내장 객체 메서드인 Date 를 이용해 페이지를 생성한 날의 날짜를 표기합니다.
2. TodoEditor : 할 일 생성
TodoEditor 컴포넌트는 할 일의 제목과 내용을 작성해 '추가' 버튼을 눌렀을 때, TodoList 에 생성될 TodoItem 컴포넌트들을 생성하는 컴포넌트입니다. 만약, 제목이나 내용 중 둘 중 하나라도 작성하지 않을 경우, 이를 useRef 리액트 훅을 이용해 해당 input 태그를 변수 선언해 포커싱해줍니다.
import React, { useRef, useState } from 'react';
import './TodoEditor.css';
function TodoEditor({ onCreate }) {
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const titleRef = useRef();
const contentRef = useRef();
const onChangeTitle = (e) => {
setTitle(e.target.value);
};
const onChangeContent = (e) => {
setContent(e.target.value);
};
const onSubmit = () => {
if (!title) {
titleRef.current.focus();
return;
} else if (!content) {
contentRef.current.focus();
return;
}
onCreate(title, content);
setTitle('');
setContent('');
};
const onkeydown = (e) => {
if (e.keyCode === 13) {
onSubmit();
}
};
return (
<div className='TodoEditor'>
<h4>새로운 Todo 작성하기 ✏️</h4>
<div className='editor_wrapper'>
<input
ref={titleRef}
value={title}
onChange={onChangeTitle}
placeholder='new TodoTitle..'
/>
<input
ref={contentRef}
value={content}
onChange={onChangeContent}
onKeyDown={onkeydown}
placeholder='new TodoContent..'
/>
<button onClick={onSubmit}>추가</button>
</div>
</div>
);
}
export default TodoEditor;
onChangeTitle, onChangeContent 함수에서 useState 를 통해 빈 칸 내용이 변화하면 즉각 렌더링되게 합니다.
onSubmit 함수는 제목과 내용 중 둘 중 하나라도 작성하지 않을 경우, focus() 메서드를 통해 이용자에게 UX 를 부여하고, 올바르게 작성이 되었다면, props 를 통해 부모 컴포넌트인 App.js 에서 받아온 onCreate 가 함수 내에서 호출됩니다. 정상적으로 호출되고 난 후, 제목과 내용이 들어가는 빈 칸은 setTitle 과 setContent 를 통해 비워줍니다.
3. TodoList : 할 일 렌더링
TodoList 컴포넌트는 TodoEditor 를 통해 생성된 할 일이 담긴 todo 배열을 map 메서드를 통해 TodoItem 컴포넌트를 반복해 나열합니다.
import React, { useState } from 'react';
import './TodoList.css';
import TodoItem from './TodoItem';
function TodoList({ todo, onComplete, onDeleteTodo }) {
const [search, setSearch] = useState('');
const onChangeSearch = (e) => {
setSearch(e.target.value);
};
const getSearchResult = () => {
return search === ''
? todo
: todo.filter((it) =>
it.content.toLowerCase().includes(search.toLowerCase())
);
};
return (
<div className='TodoList'>
<h4>Todo List🤓</h4>
<input
value={search}
onChange={onChangeSearch}
className='searchbar'
placeholder='검색어를 입력하세요'
/>
<div className='list_wrapper'>
{getSearchResult().map((it) => (
<TodoItem
key={it.id}
{...it}
onComplete={onComplete}
onDeleteTodo={onDeleteTodo}
/>
))}
</div>
</div>
);
}
export default TodoList;
TodoEditor 에서 onSubmit 내에서 호출된 onCreate 함수를 통해 useState 를 통해 선언된 todo 가 setTodo 에 의해 상태가 변화하면서 새롭게 렌더링이 되고, 업데이트된 todo 를 다시 App.js 에서 TodoList 로 props 를 전달됩니다.
TodoItem 을 렌더링하기 전, getSearchResult 함수를 통해 검색 기능을 추가합니다.
map 메서드를 통해 getSearchResult 에서 반환된 값을 TodoItem 으로 화면에 렌더링 될 배열의 요소들을 props 를 전달해 반복해 렌더링될 것을 요구합니다.
4. DoneList : 완료한 일 렌더링
DoneList 는 TodoList 에서 생성된 TodoItem 에서 완료 버튼을 클릭했을 때, 할 일이 완료된 TodoItem 들을 useState 를 통해 선언된 done 이라는 변수에 담아 이를 map 으로 렌더링합니다.
import React, { useState } from 'react';
import './DoneList.css';
import DoneItem from './DoneItem';
function DoneList({ done, onCancel, onDeleteDone }) {
const [search, setSearch] = useState('');
const onChangeSearch = (e) => {
setSearch(e.target.value);
};
const getSearchResult = () => {
return search === ''
? done
: done.filter((it) =>
it.content.toLowerCase().includes(search.toLowerCase())
);
};
return (
<div className='DoneList'>
<h4>Done List😎</h4>
<input
value={search}
onChange={onChangeSearch}
className='searchbar'
placeholder='검색어를 입력하세요'
/>
<div className='list_wrapper'>
{getSearchResult().map((it) => (
<DoneItem
key={it.id}
{...it}
onCancel={onCancel}
onDeleteDone={onDeleteDone}
/>
))}
</div>
</div>
);
}
export default DoneList;