노션 클로닝 프로젝트 회고
2023.09.23
Table of Contents
데브코스 4번째 과제로 노션 클론 코딩을 진행하였습니다. 코드 리뷰 반영을 진행한 후 작성하는 회고 글입니다.
리팩토링을 진행하였지만 아직 부족한 점이 많아서 시간이 생길 때마다 조금씩 개선할 계획입니다.
- 바닐라 JS만을 이용해 노션 클로닝을 진행하였습니다.
- 컴포넌트 방식, history API를 활용해 SPA로 구현하였습니다.
- 기본 요구사항은 모두 구현하였고 보너스는 한 가지 구현하였습니다.
- 초기 목표는 최소한으로 데이터를 fetch 하는 것을 목표로 하였습니다.
- 스타일을 줄 때에는 CSS와 자바스크립트 inline 모두 활용하여 주었습니다.
🤔구현 기능
🎄트리 구조 구현
이제는 (구)를 앞에 붙여야 하지만 오프라인 학습이 진행되어 팀원들과 만나서 과제를 진행하였고 첫날 우리 팀의 목표는 트리구조 구현이었다. 구현 로직은 대부분 비슷하게 생각하였고 본인과 같은 경우에도 재귀를 사용해서 트리를 탐색하며 하위 문서가 없을 때까지 내려가는 것으로 구현하였다. 따로 배열을 만들고 push 하며 구현하는 것이 아닌 하위 문서의 length만을 확인하며 진행되어서 복잡하지 않게 구현할 수 있었다. 팀원들과 같이 있을 때는 구현하지 못하다가 숙소로 돌아오니 생각이 나서 카페에 들러 구현하고 숙소로 돌아갔다😅
🪤 구조 설계
설계의 필요성을 느끼게 된 계기는 상태를 효율적으로 전달하는 방법에 대해 생각해보고 설계 없이 진행하게 되면 분명 돌이킬 수 없는 문제가 생길 거라고 느꼈기 때문이다. 또한 설계에 대한 부분은 커피 챗을 통해 Top-down과 Bottom-up 설계 방식에 대해 멘토님께서 설명해주셨다. 개인적으로 맞는 방법은 Top-down이었으나 의존성이 발생할까 너무 걱정한 점이 문제였던 것 같다. (두 가지 설계 방식에 대해 알게 된 시점은 이미 다시 시작하기엔 늦은 시점이었다.) 크게 두 파트 Sidebar, Edit page로 구분하였다.
🚪열림/닫힘 표시
이번 과제를 진행하며 이 부분이 아쉬움이 많이 남는다. 초기 계획은 fetch를 최소화하고 새로고침이 없다면 굳이 열림/닫힘 상태를 저장해야 할 필요가 없을 것이라고 생각하여 LocalStorage를 사용하지 않았고 이 부분이 스노우볼이 되었다.
초기에 구현할 때에는 DOM 조작을 통해 문제없이 구현하였고 잘 동작하였지만 다른 기능들이 추가될수록 문제점이 발생하였다.
여러 가지 문제점이 있었지만 우선 낙관적 업데이트만으로 해결할 수 없는 부분이 있었다. 이번에 활용한 API는 하위 문서가 있는 상위 문서를 삭제하면 하위 문서들이 root로 올라오는데 이를 표현하기 위해서는 refresh를 진행해야만 했다.(낙관적 업데이트로 진행하게 되면 오류가 발생할 가능성이 너무 커 보였다.)
리팩토링을 진행할 때 로컬 스토리지에 각 문서들의 열림/닫힘 표시를 저장하는 것을 1순위로 처리할 계획이다.
State
개인적으로 이번 과제를 진행하며 상태 값의 흐름에 대해 많이 생각해보고 배운 점이 많았다. 간략하게 설명하자면 App에서 필요한 데이트를 init 함수를 통해 받아오고 문서 리스트는 sidebar에만 문서에 대한 데이터는 Edit page로만 전달하려고 노력하였다. 불필요한 데이터를 줄여서 구현해보고 싶은 욕심이 있었다. 결과적으로 구현에 성공하였고 장점과 단점은 느낀 점에 정리해보려고 한다.
🕰️ history API
강의를 열심히 들은 나를 칭찬해주고 싶었다. 물론 어려운 내용은 아니지만 이번 과제를 SPA로 구현하는데 핵심인 내용이고 route에 따른 데이터로 페이지를 표시하는 방식으로 진행했기 때문에 복잡한 부분을 간결하게 생각할 수 있었다. 또한 route를 통해 데이터를 가져오는 과정에서 innerHTML을 비우고 채우기 때문에 깜빡거림이 발생하는 문제가 있었고 이를 해결하기 위해 innerHTML을 사용하지 않고 prepend로 순서에 맞게 요소를 추가하여 화면이 깜빡거리는 현상을 없앨 수 있었다.
🍞breadcrumb
기본, 추가 구현 사항 중에는 해당 내용이 따로 없었지만 노션에는 있는 기능이어서 추가로 구현해보게 되었다. 트리 구조를 구현해본 경험이 있어서인지 어렵지 않게 구현할 수 있었다. DOM 조작이 많이 들어가긴 하였지만 재귀를 사용해서 코드가 간결하고 구조에 대해 알고 있다면 이해하기에도 어렵지 않아 보인다. (멘토님의 👍을 받아 뿌듯했다)
클릭하면 해당 경로로 들어가고 상태 값의 변화를 확인하는 함수를 작성하여 현재 경로를 눌렀을 때에는 fetch가 이뤄지지 않는다.
const recursiveBC = (id, path) => { const $curLi = document.getElementById(id); if ($curLi) { const $ul = $curLi.closest("ul"); if ($ul.className === "child" || $ul.className === "child--show") { path.push([$curLi.querySelector("span").innerHTML, id]); recursiveBC($ul.closest("li").id, path); } else { path.push([$curLi.querySelector("span").innerHTML, id]); } } };
🔗 subdocument link
유일하게 구현한 추가 구현 사항으로 이 부분은 DOM으로 접근하는 게 아닌 상태 값을 활용하였고 문서 아래에 표시하였다.
클릭하면 sidebar의 문서가 열림 표시가 되며 하위 문서로 이동하게 된다.
♻️ 리팩토링을 진행한 부분들
- api 중복 바인딩 한 줄로 처리
- HTML inline style 속성을 CSS로 수정
- breadcrumb breadcrumb 방어 코드, 메서드 분리, validation 추가"
- checkdiffrence 함수 추가
- Editor 이벤트 디스트럭팅 변수 개선
- SubLink 삼항 연산자로 render 수정, 방어 코드 추가
- class로 display 속성 변경, 불필요한 async 삭제
- Local storage에 열린 파일들을 담아 놓고 렌더링시 이를 활용하여 작업 정보를 기억
🙌 느낀 점
이번에 완성한 프로젝트는 초기 생각했던 방식으로 구현에 성공하였고 fetch의 횟수를 줄이고 낙관적 업데이트로 빠른 렌더링을 제공하는 장점이 있지만 문서 리스트를 refresh 해야 하거나 DOM 조작이 과하게 들어갔다고 느껴져 아쉬움도 큰 과제였다.
점진적인 리팩토링에 대한 특강 듣게 되어 조금 더 이 프로젝트에 애정을 가지고 개선해보고 싶다. contenteditable이나 아직 발견하지 못한 에러에 대한 방어 코드들을 조금씩 더 생각해보려고 한다. 또한 배포해서 공유를 해보고 싶었으나 환경변수의 벽에 막혀 다른 방법을 강구해야 할 것 같다.🥲
밤을 새기도 처음부터 다시 구현할까라는 생각도 하였지만 데드라인 안에 PR까지 작성한 것에 성취감을 느꼈다. 혼자였다면 멘탈적으로 아주 힘들었겠지만 팀원들과 함께 고민하며 진행하여 돌아보니 그렇게 나쁘진 않은 시간이었던 것 같다.