지난번의 타이머 앱처럼 리액트에 대한 감을 되찾고자, 간단히 진행해 보았습니다.
투두 리스트는 일반적으로 날짜 개념이랑 엮어서 많이 쓰는데, 기존에 클론 코딩했던 Velopert님의 투두 리스트를 다시 한 번 더 클론 코딩하면서 기능을 추가해 보았습니다.
날짜에 대한 Todo 정보는 브라우저의 LocalStorage
에 저장해서, 서버 없이 배포했으므로 GitHub Pages를 이용했습니다.
만약, 이용 후 로컬 스토리지에 정보가 남는 것이 싫으신 분들은 개발자 도구(F12)의
애플리케이션 탭 -> 로컬 스토리지
에서 todo-list의 정보를 삭제해주세요. 추후에 Remove 동작을 하면 로컬 스토리지에서도 삭제되도록 개선할 예정입니다.
react-datepicker
라는 라이브러리를 이용해서 달력 시스템을 가져왔습니다.메인 화면이자, 모든 동작의 시작이 되는 컴포넌트 Calendar.jsx입니다.
const [startDate, setStartDate] = useState(new Date()); const [openTodoList, setOpenTodoList] = useState(false); const [todos, setTodos] = useState([]); const nextId = useRef(1); const modalBackground = useRef(); const todoData = JSON.parse(localStorage.getItem('todo-data') || '[]');
- startDate:
react-datepicker
에서 사용되는 state입니다. 캘린더의 기본 시작날짜(오늘)과 변경되고 선택된 날짜 정보를 담습니다.- openTodoList: 날짜를 선택하고 모달창을 열게 만드는 플래그 state입니다. 모달화된 todo list의 조건부 렌더링에 사용됩니다.
- todos: 모든 todo 정보가 담기는 state입니다.
- nextId: todo에 번호를 부여하는 ref입니다.
- modalBackground: 모달창의 바깥 부분을 클릭했을 때, 화면이 닫히도록 조작하기 위한 ref입니다.
- todoData: todo를 로컬 스토리지에 저장하기 위해 사용되는 변수입니다.
const onChange = date => { setStartDate(date); setOpenTodoList(true); };
onChange는
date-picker
에서 startDate를 변경하기 위해 사용되는 함수입니다. 여기에 날짜를 변경하면 모달창이 열리도록 openTodoList state를 true로 바꿔주었습니다.
const onInsert = useCallback(text => { const todo = { id: nextId.current, text, checked: false, month: startDate.getMonth() + 1, date: startDate.getDate(), }; setTodos(todos.concat(todo)); todoData.push(todo); //로컬 스토리지에 todo 삽입 localStorage.setItem('todo-data', JSON.stringify(todoData)); nextId.current += 1; }, [todos, startDate, todoData]);
onInsert는 todo-list에서 todo를 추가하는 동작을 담당합니다. todo객체에는
id
, 내용인text
, todo의 달성 상태를 나타내는checked
, 날짜 정보를 위한month/date
가 있습니다.todo를 입력하면
concat()
으로 todos에 이어 붙입니다.그 후 todo를 로컬 스토리지에 넣는 과정을 거칩니다.
registerLocale('ko', ko); //DatePicker에 한국 시각을 적용하기 위한 locale 설정 const todoDataOfSelectedDate = todos.filter(todos => todos.month === (startDate.getMonth() + 1) && todos.date === startDate.getDate() );
todoDataOfSelectedDate
는 todos에서 선택한 날짜의 todo 정보를 가져오기 위한 함수입니다. todos의 month와 date를 선택한 날짜의 month, date와 비교해서 일치하는 todo들만 가져와서 렌더링해줍니다.
return ( <> <Header /> <div className={'date-picker-container'}> <div className={'date-picker'}> <DatePicker className={'datepicker'} locale={'ko'} dateFormat={'yyyy년 MM월 dd일'} dateFormatCalendar={'yyyy년 MM일'} selected={startDate} onChange={onChange} showIcon /> </div> </div> {openTodoList && <div className={'todo-list-container'} ref={modalBackground} onClick={e => { if (e.target === modalBackground.current) { setOpenTodoList(false); }}} > <TodoTemplate> <TodoInsert onInsert={onInsert} /> <TodoList todos={todoDataOfSelectedDate} onRemove={onRemove} onToggle={onToggle} /> </TodoTemplate> </div> } </> );
컴포넌트 return 구문입니다. openTodoList의 값에 따라 모달화된 todo list가 조건부 렌더링 됩니다.
수정할 예정인 문제점들입니다.
id
가 1부터 이어져야하는데, 총 갯수를 기준으로 id가 카운트 된다.Calendar.jsx
가 너무 많은 일을 한다.비록 클린 코딩의 결과물이었지만, 어떠한 프로그램에 추가 기능을 확장해보는 경험을 할 수 있었다.
react-datepicker
라는 편리한 라이브러리를 새로 알게 되었다.
Calendar.jsx
컴포넌트가 너무 난잡하다. 더 작고, 한 가지 일만 하도록 재사용성을 신경써서 다시 만들어야할 것 같다. -> 즉, 클린 코드는 없었다.
이번엔 사실 react-datepicker
라는 라이브러리를 쓸 이유가 전혀 없었다. 차라리 달력을 직접 구현할 걸하는 생각이 완성하고 나니까 들었다.
사실 처음에는 <DatePicker>
컴포넌트에 inline
속성을 주어 항상 펼쳐진 상태의 달력을 구현하려고 했었다.이런느낌으로 가려고 했는데, 이렇게 하니까 startDate
의 렌더링 과정에서 문제가 생겼었다.
렌더링 과정이
날짜 클릭 -> 선택된 날짜 선택효과 렌더링 -> 모달 오픈
순서로 이우러져야 했는데, 실제로는날짜 클릭 -> 모달 오픈 -> 모달을 닫아야 선택된 날짜 렌더링순
으로 이루어져서 날짜를 클릭해도 startDate가 업데이트 되지 않는 문제가 있었다.이것은
<DatePicker/>
컴포넌트 자체의 문제라inline
속성으로 화면을 깔끔하게 만들 수가 없었다.
이러한 문제점 때문에 초기 설계와는 전혀 다른 방향으로 개발해야했고, 결과물도 내 예상과 다른 방향의 물건이 만들어졌다. 이러한 일들을 방지하려면 라이브러리에 대한 조사를 좀 더 면밀히할 필요가 있다고 느끼게 되었다.
그리고 결과물을 확인하니 처음에 생각했던대로 달력을 직접 만들었으면 어땠을까 하는 아쉬운 마음이 남았다.