DYO 공부하는 블로그
[MINI] TODOLIST 프로젝트 후기 본문

-🎯 프로젝트 개요
- 프로젝트명: GrowWeed ToDo List
- GitHub: https://github.com/yoon5450/Team_GrowWeed_To-Do-List
- 목표: DOM과 localStorage를 이용한 기본 기능 구현 + 구조적 코드 분리 학습
- 기능 요약:
- 할 일 추가/삭제
- localStorage 저장 및 초기 불러오기
- 완료/미완료 상태 관리
- 프로젝트 진행 계획 (팀장)
기능을 엄청나게 여러 개 만들고, 디자인 레이아웃도 멋지게 담고 애니메이션도 설계해서 하려고 할 수도 있었다. 그러나 근본적으로 이 프로젝트를 진행하는 곳은 모두 성장을 해야 하는 부트캠프다. 프로젝트의 평가 결과로 성적이 매겨지는 것이 아니라서 처음부터 프로젝트 계획을 러프하게 잡고 본인이 할 수 있는 영역에서 기능을 추가하도록 하고자 했다. (프로젝트에 매몰되면 진도가 빠른 특성상 복습은 꿈도 못 꾼다.) 과제 요구사항을 보고 프로젝트 진행에 대해서 개략적인 계획을 공지했다.
- 기본 기능 :
추가, 삭제, localstorage 저장, 초기 로드, +마친 일 state 관리
- 프로젝트 수행 순서 :
디자인(노드 사이즈 정의) - html 스켈레톤 - 기능 구현 - css 디자인 - css 애니메이션
- 함수명 :
함수명 컨벤션은 이미 제시되어있음. 가능한 그대로 이용함. 정의되어 있는 함수끼리의 연결성, return을 어떤 형태로 줄 것이냐, 난이도 분류 작업을 하고 요구사항 분배.
- 프로젝트 요구 폴더 구성 :
폴더 구성은 모듈형으로 프로젝트를 진행할 수 밖에 없는 구성으로 되어 있음. 모듈형에서 필수인 객체-메서드 구조 모든 팀원에게 반드시 공부하도록 함.
- Git 사용 :
숙련도가 낮아 짧은 진행 시간에 세팅하기 어려울 것 같아 나중에 합치기로 함.
- 본인 진행중인 상황 추적 :
항상 본인이 하고 있는 작업이나 아이디어는 추적되어야 한다는 원칙을 세웠다. 누가 작성했는지 확인할 수 있어야 하고, 누가 아이디어를 냈는지 알고 있어야 한다. 남는 게 없는 프로젝트는 시간낭비라고 생각하기 때문이다.
- 기술 선정 :
localStorage, DOM 이벤트, 불변성(immutability) 등 기초 기술에 집중. crypto.randomUUID() 기반으로 유니크 ID 생성 및 관리, localStorage에 배열(JSON) 기반으로 저장하고, map / filter 등 표현식 중심 조작
TODOLIST는 제작하기 어렵지 않아서(레퍼런스도 정말 많음) 각각 프로젝트를 진행한 뒤에 나중에 함수 위주로 각각 진행한 부분을 주석으로 남겨 기여를 넣고자 했다
프로젝트 본격적인 진행 들어가기 전 가장 크게 고민했던 부분은 '결국엔 내가 다른 사람 코드 보고 직접 합쳐야 되는데, 발생하는 문제가 뭐가 있을까?' 생각하다가 내 코드 설계에서 생각해야 할 부분에 대해서 생각해봤다.
1. 가장 먼저 해야 할 일이 JS는 각각 따로 작성하더라도 '같은 화면, HTML id, class에 대해서 기능 설계를 해야겠구나.'라는 생각이 들었다. id, class 컨벤션을 맞추고 디자인을 맞춰 최대한 합칠 때 혼선이 없도록 해야겠다는 생각이 들었다.
2. 내 코드를 기능 단위로 세세하게 쪼개야겠구나. 한 함수에서 수행하는 기능들은 각각 다를 거라고 생각했다. (누군가는 한 함수에서 여러 기능을 수행할 거고, SRP를 지킨 상태로 함수를 만드는 사람도 있을 것이다. 최대한 쪼개 놓으면 함수 자체를 교체하더라도 큰 문제가 없을 것 같았다.
- 간단한 기능 요구 요약
- TODO 항목 추가 / 삭제
- localStorage 저장
- 초기 로드 시 데이터 불러오기
- 완료 항목 상태 관리
- 프로젝트 진행
가장 먼저 해야 할 디자인과 HTML 스켈레톤 구성은 주말 사이에 내가 이미 해 와서 건드릴 부분이 없었는데, 레이아웃 배치 CSS 작업은 기본적으로 해야 할 것 같아서 '혹시 하실 분?' 하고 물었는데 아무도 하고 싶은 사람이 없었다.

그럼 뭐, 팀장이 해야지 😢 휴일이 3일 끼어 있어서 프로젝트 진행이 이미 빠듯한 상황이었기 때문에 기존 스켈레톤은 GIT에 두고 당일까지 작성해서 GIT에 올려 주기로 했다.
레이아웃이 상당히 단순한 편이라 거의 모든 항목을 vw로 작성해서 반응형으로 보이게 했다. 개체 비율 / 1920만 하면 되니까. 엘리먼트가 얼마 없어서 쉽기도 했고, 듀얼모니터로 작업을 태블릿으로 해서, 화면 비율이 좀 망가져 있어 하는 김에 반응형으로 설계했다.
한시간 반쯤 작업하고 당일에 바로 넘겨줬고, 그제야 나도 내 개인 작업을 할 수 있었다. 지속적으로 기존 코드에 기능 추가, HTML 등의 공통 코드에 영향을 미치는 부분에 대해서 팀원들과 소통하고, 어려운 부분에 대해서도 나누면서 기본 기능 + 추가 기능을 구현할 수 있었다.
그런데 중간쯤에 쉽게 프로젝트를 진행하는 인원도 있었지만 함수 단위로 기능을 각각 나누는 것을 어려워하는 팀원이 있는 것 같아서 내 코드 기반해서 함수의 기능과 실행 구조를 설명하고 최대한 다른 인원들도 구현할 수 있도록 하려고 했다.
- 프로젝트 진행하면서 학습한 것
1. 불변성 유지와 배열 처리 방식
map, filter, concat 등의 return값이 있는 식을 이용해 원본 데이터를 건드리지 않고 새 배열 반환하는 방식으로 관리
이와 같은 방식이 React 같은 프레임워크에서 상태 비교를 위한 얕은 비교와 직결되기도 한다는 것을 알았다.
const newList = todoListArray.filter(item => item.id !== id);
2. localStorage 데이터 구조 설계
localStorage는 String만 저장할 수 있는 구조이기 때문에, 단일 배열로 객체를 저장하는 구조상 JSON.stringify()와 JSON.parse()를 이용해 관리해야 한다는 것을 알았다. 서버와 통신할 때도 크게 다르지 않다.
Date 객체로 날짜를 관리했었는데, 파싱할 때 객체 메서드가 사라지는 점을 발견하고 불러올 때마다 new Date()로 변환하도록 했다.
3. form 사용의 시맨틱 설계
keydown으로 submit을 하는 설계여서 form 태그가 필요 없을 것 같았는데, (필요 없는 submit 동작을 하도록 한다는 것도 한몫 하고.) 시맨틱 구조상 유리하다는 점을 배웠다. e.preventDefault()로 submit 기본 동작을 막고, 키 입력과 버튼 클릭을 같은 동작으로 처리할 수 있다는 걸 알았다.
form.addEventListener("submit", e => {
e.preventDefault();
});
4. 범위를 좁힌 이벤트 위임과 DOM 제어
생성된 요소에 대해서는 최초 생성된 DOM tree에서 읽는 것이 아니기 때문에 event 탐지가 불가능하다. 그런데 나는 이벤트 위임을 처리할 때 항상 document를 이용해서 위임했었는데, ul 등의 태그로 범위를 좁힐 수 있다는 것을 알았다.
todoUl.addEventListener("click", (e) => {
const id = e.target.closest("li.todo-list-cell").dataset.id;
// 할 일 완료 이벤트
if (e.target.closest(".todo-list-complete-btn")) {
handleComplete(id);
return;
}
// 할 일 제거 이벤트
if (e.target.closest(".todo-list-optional")) {
handleRemove(id);
}
});
5. SPA에서의 렌더링 최적화 전략
완료, 미완료 탭을 나눠 설계하고, 추가될 때마다 submit 동작에서 화면이 새로고침 되어 init 되는 구조가 아니었기 때문에, 탭을 넘어가거나 아이템이 추가될 때마다의 전략을 알아야 했다.
tabFlag를 활용해 완료/미완료를 한 화면 내에서 전환하는 방법을 배웠다.
6. 전역 상태의 최소화
todoListArray라는 todo목록 배열을 전역으로 관리하는지 고민했는데, 불변성 유지를 위해서는 매번 getStorage를 이용해 불러오는 것이 더 좋은 방법일 것 같았다. 전역에 의지하고 싶지도 않았고.
7. 핸들러 함수 분리
onclick 이벤트 내부에서 아이템 객체 생성, 아이템 엘리먼트 생성, localStorage 불러오기, 저장 등의 많은 기능을 수행하다 보니 onclick 이벤트 callback 내부가 너무 번잡하게 보였다. handleAdd, handleComple등으로 분리하며 이벤트 리스너를 최대한 깔끔하게 유지하려고 했다.
function handleAdd() {
// validation
if (inputToDoText.value.trim() === "") {
return;
}
// id : 난수 id, value : 입력값(Contents), itemObj : 아이템 객체, updated : 새로운 아이템 객체가 추가된 Array
let id = self.crypto.randomUUID();
let value = inputToDoText.value;
let itemObj = createItemObject(id, value);
let todoListArray = getStorage();
let updated = addItemArray(todoListArray, itemObj);
setStorage(updated);
renderItem(itemObj);
inputToDoText.value = "";
}
8. 함수의 depth 관리
핸들러 함수에서 관리하더라도 localStorage에 아이템에 추가된 배열 저장할 때 item 변환, 기존 localStorage 불러오기, 배열에 객체 저장, localStorage에 저장과 같은 과정을 거쳐야 했기 때문에, depth가 3을 넘는 경우가 종종 있었다. 그래서 depth가 너무 깊어질 것 같으면 파라미터를 늘리던가 하는 방법으로 관리했다.
9. 디렉터리 설계와 모듈화
/lib/dom.js, /lib/storage.js 등으로 헬퍼 함수 분리하는 법을 배웠고, render, createItem, init 등을 어떻게 분리할지 고민하며 depth 2 이상 넘기지 않기 설계 철학을 유지하려고 했다.
10. CSS clamp
글자 크기까지 vw로 설계했더니 문제가, 가로 길이가 너무 짧아질 때는 끝도 없이 글자가 작아진다는 점이었다. clamp를 이용해 최소 크기, 최대 크기를 조절할 수 있다는 걸 배웠다.
font-size: clamp(14px, 2vw, 24px);
- 프로젝트 진행 후 아쉬운 부분
유틸 함수를 모듈로 분리한 김에 모듈로 구분했으면 어땠을까. 하는 아쉬움이 있긴 하다.
- 마무리 후기
전체적으로 나는 진행도 만족하고 내 코드도 내 수준에서는 함수 분리가 잘 된 것 같아서 만족하는 프로젝트였다. 진행도 계획한 대로 된 것 같고, 설계상도 내가 생각한 대로 된 부분이 많았다.
각자 TODOLIST 만든 내용에서 함수나 이벤트리스너 등을 떼어와서 내 프로젝트로 merge했는데, 기능 분리가 기존보다 나빠진 부분도 있긴 했다.(물론 내 기준) 내 입맛대로 모든 코드를 수정하는 게 '팀' 프로젝트가 아니라는 것은 알고 있어서, 팀 원들의 기여와 코드는 원래 로직대로 최대한 남기려고 했다. 물론 처음에 정했던 컨벤션이나, localStorage 사용, 불변성 위반 등은 예외로 조금 고쳤고.
다음부턴 내가 메인 담당으로 코드 보면서 merge 안 한다. 남 코드 읽어보고 충돌 안 나게 테스트해보고 하는 과정이 생각보다 시간이 오래 걸렸고 머리도 정말 많이 굴려야 해서 그 당일에는 그것 말고 다른 건 아무것도 못 했던 것 같다. 다음에는 브랜치 분리를 하던가, 담당 파일을 아예 분리해버리고 git add . 금지 등과 같은 규칙을 정해서 git을 쓰는 게 오히려 마음이 편할 것 같다. 아무리 팀장이라도 나도 내 공부가 있고, 철인이 아니니까 책임을 분배하는 게 더 좋지 않을까.