티스토리 뷰

10일간의 길었던? 노션 클론 프로젝트가 끝이 났다.

 

일단 결과물과 코드

JooNotion

 

🔳 JooNotion

 

joo-notion.vercel.app

https://github.com/chunwookJoo/JooNotion

 

GitHub - chunwookJoo/JooNotion

Contribute to chunwookJoo/JooNotion development by creating an account on GitHub.

github.com

 

이 프로젝트를 진행하기 전에 1기 선배님들의 화려한 노션 클로닝 프로젝트를 보고 나도 저렇게 멋지게 만들고 싶다는 욕구가 활활 타올랐다.

 

회고록까지 완벽하게 프로젝트를 끝낸 선배님들을 보면서

나도 개발하면서 고민했던 부분, 이슈들 다 기록하면서 개발해야지 ㅎㅎ

 

하지만 현실은

(계획을 하지만 계획대로 되는게 하나도 없는 J 입니다 ^^;)

 

 

그래도 열흘간 정말 이 프로젝트에만 집중하고 잘 만들고 싶다는 욕구 때문이었는지 금방 잊어버리진 않았고 더 늦기 전에 기록해놓아야겠다는 생각이 들었다.

 

📌 기능 구현 기본 요구사항

바닐라 JS만을 이용해 노션을 클로닝합니다.기본적인 레이아웃은 노션과 같으며, 스타일링, 컬러값 등은 원하는대로 커스텀합니다.

1. 글 단위를 Document라고 합니다. Document는 Document 여러개를 포함할 수 있습니다.
2. 화면 좌측에 Root Documents를 불러오는 API를 통해 루트 Documents를 렌더링합니다.
3. Root Document를 클릭하면 오른쪽 편집기 영역에 해당 Document의 Content를 렌더링합니다.
4. 해당 Root Document에 하위 Document가 있는 경우, 해당 Document 아래에 트리 형태로 렌더링 합니다.
5. Document Tree에서 각 Document 우측에는 + 버튼이 있습니다. 해당 버튼을 클릭하면, 클릭한 Document의 하위 Document로 새 Document를 생성하고 편집화면으로 넘깁니다.
6. 편집기에는 기본적으로 저장 버튼이 없습니다. Document Save API를 이용해 지속적으로 서버에 저장되도록 합니다.
7. History API를 이용해 SPA 형태로 만듭니다.
8. 루트 URL 접속 시엔 별다른 편집기 선택이 안 된 상태입니다.
9. /documents/{documentId} 로 접속시, 해당 Document 의 content를 불러와 편집기에 로딩합니다.

📌 보너스 요구사항

- 기본적으로 편집기는 textarea 기반으로 단순한 텍스트 편집기로 시작하되, 여력이 되면 div와 contentEditable을 조합해서 좀 더 Rich한 에디터를 만들어봅니다.
- 편집기 최하단에는 현재 편집 중인 Document의 하위 Document 링크를 렌더링하도록 추가합니다.
- 편집기 내에서 다른 Document name을 적은 경우, 자동으로 해당 Document의 편집 페이지로 이동하는 링크를 거는 기능을 추가합니다.
그외 개선하거나 구현했으면 좋겠다는 부분이 있으면 적극적으로 구현해봅니다!

 

요구사항 중에 큰 기능들은 강의에서 진행했던 내용들이여서 구현하는건 크게 어렵지 않게 할 수 있었다.

예를 들어서, 요구사항 6번같은 경우에는 디바운싱을 이용해서 구현해야하는데 강의를 듣고 구현을 하니 쉽게 이해할 수 있었다.

디바운싱이란?
연속으로 호출되는 함수들 중에 마지막에 호출되는 함수(또는 제일 처음 함수)만 실행되도록 하는것.

let timer = null;
const editor = new Editor({
    $target,
    initialState: this.state,
    onEditing: (post) => {
        if (timer !== null) {
            clearTimeout(timer);
        }
        timer = setTimeout(async () => {
            const updatedPost = await request(`/documents/${post.id}`, {
                method: "PUT",
                body: JSON.stringify(post),
            });
            updatePost(updatedPost);
        }, 1000);
    },
});​
Editor 컴포넌트에서 onEditing으로 입력을 받고, 입력받는 도중에 1초간 입력이 없으면 자동으로 저장이 된다.

 

프로젝트 목표 (였던 것)

첫 시작엔 의욕이 넘쳐나서 이것저것 넣고 싶은 기능들이 많았다. 일단 기본 기능 구현은 다 동작한다는 가정하에 추가로 넣고 싶었던 기능들이 다음과 같다.

  • 다크모드
  • Document 리스트 사이즈 resize
  • Redux처럼 전역상태관리 구현
  • contenteditable로 rich editor 만들기
  • Modal 창 구현
  • 404 페이지

이런 큰 꿈을 안고 호기롭게 컴포넌트 설계를 했다. 위 리스트들 중에서 꼭 만들어보고싶었던 것은 전역상태관리를 만드는 것이었다. 리액트로 프로젝트를 할 때 Redux개념이 살짝 어려워서 Recoil로 갈아탔었는데 이번 기회에 Redux에 대해 완벽히 이해하고 구현해보자라는 다짐을 했었다. 

하지만 이게 문제가 되어 돌아왔다.

 

프로젝트는 한번 갈아엎어야 제 맛이지!

Redux개념을 잘 이해하지 못한 상태에서 오로지 바닐라 자바스크립트로만 구현하려니 더 이해하기가 어려웠다. 그래도 몇시간 동안 카페에 앉아서 이해해보겠다고 주석에 막 적어가면서 노력했지만 끝내 실패했다..

노력의 흔적...

여기서 끝이 아니였다.

전역상태관리 구현에 도전하기 전에 글 목록 구현부터 애먹었었다.

실제 노션의 글 목록 예시

처음 이 글 목록을 어떻게 할까 생각을 많이 했다. 생각이 너무 많았던게 탈이었다.

먼저 api 목록조회는 했지만 그 안에 있는 Documents 배열들을 어떻게 토글 버튼을 누를때 보여줄까..? 이 생각을 하다가

왜때문인지 "토글 버튼이 open이면 그때 렌더링해줘야겠다" 라는 이상한 생각에 빠지게 되었고 거기에서도 시간을 많이 잡아먹었다 :(

 

그리고 HomePage에서 404페이지를 보여줄 때도 사이드에 있는 글 목록이 계속 나오는 등.. 버그가 쌓이고 쌓이는 현상이 발생했다.

(이건 어이가 없어서 녹화해뒀다 ㅋㅋ...)

 

 

과유불급 이라는 말을 되새기며 결국 프로젝트를 한번 갈아엎었다.

 

 

프로젝트 재설계

이때부터 컴포넌트의 구조에 대해 생각해보았다.

컴포넌트 구조

이렇게 컴포넌트 구조와 데이터 플로우를 그려보기 전엔 데이터가 어디로 가야되는지 코드를 짜면서도 길을 잃은 적이 몇번 있었다. 하지만 이렇게 한번 시각화 해보니 길을 잃더라도 금방 무엇을 해야되는지 알아차릴 수 있었고, 이런 시각화의 중요성에 대해 느낄 수 있었다.

 

이때부턴 많은 욕심내지 않고 기본 요구 사항이라도 제대로 구현하고, 이해하고 넘어가자 라는 마인드로 차근차근 개발해나갔다. 강의에 나온 코드를 그대로 갖다 붙히는 것이 아니라 그 코드보다 더 나은게 있을지 생각해보고, 내 프로젝트에 맞는 코드로 하나씩 고쳐나갔다.

 

각 컴포넌트의 역할

App.js

  • 메인 페이지 (PostsPage.js, HomePage.js PostEdit.js) 생성
  • 404 페이지 생성
  • pathname에 따른 라우팅 처리

HomePage.js

  • pathname이 루트일때 보여주는 화면

NotFound.js

  • 404에러 보여줌
  • 홈으로 이동 네비게이션

PostsPage.js 

  • 하위 컴포넌트 생성
  • 하위 컴포넌트에서 받아온 데이터 처리 (수정, 삭제, 하위페이지 생성)
  • UI 동기 처리

PostList.js, Editor.js

  • Click, Keyup 이벤트 처리, 부모의 이벤트 바인딩 (수정, 삭제, 하위페이지 생성)

CreatePostModal.js

  • 새 페이지 생성 Modal
  • 하위 페이지 생성 Modal

 

문제해결 과정

1. PostList 새로고침 시 list-active와 toggle-open 문제

에러 상황
어떤 글을 클릭하고 url이 바뀌었을때 새로고침을 하면 url은 그대로지만 list-active했던 class와 toggle-list class가 날아갔다. 
글 목록에서 글을 클릭했을때 URL은 "localhost:3000/documents/{document_id}" 와 같다.

list-active했던 class는 App.js에서 pathname으로 id를 추출하고, url이 바뀔때마다 (글을 클릭할 때 마다) id를 PostList.js로 넘겨줬다. PostList.js에서 리스트들을 렌더링 할 때 data-id 속성이 넘겨받은 id와 같을때 active class를 추가해주면서 문제를 해결했다.

toggle-list open을 유지 하는 것은 LocalStorage를 이용해서 해결 할 수 있었다.
toggle버튼을 클릭할때, 즉 하위페이지를 열 땐 localstorage에 글id를 배열로 저장하고, 하위페이지를 닫으면 배열에서 id를 삭제했다.
localstorage 조회, id삽입

처음 페이지가 렌더링 될 때 setState를 실행하면서 localstorage에 있는 openedId배열을 받아오고, 하위페이지를 열때 setOpenedLists를 호출하면서 id를 넣어주었다.
조회한 openedId를 templates.js로 넘겨주고 openedId배열 안에 렌더링 할 post.id가 없으면 hide클래스를 유지했다.

 

 

2. 수정, 삭제, 글 생성 시 PostList 리랜더링 되면서 class 날아가는 문제

사실 이 문제는 위 1번의 문제를 해결하면서 동시에 해결되었다.

랜더링 될 때마다 App.js에서 해당 url에 있는 id를 넘겨주고, 또 openedId는 localstorage에 저장되기 때문에 브라우저를 닫았다가 열어도 열었던 하위페이지가 유지되는 개이득을 맛 볼 수 있었다. 😋

 

 

KPT 회고

Keep (현재 만족하고, 앞으로도 유지하고자 하는 부분)

  • 단순히 클론과 기능 구현에만 그치지 않고, 프로젝트 배포를 하고 누군가한테 보여주겠다는 마음으로 개발을 진행했습니다. 누군가가 사용할 때 어느 부분들이 불편할까에 대해 생각했고, 그런 부분들을 생각하며 개발함으로써 제 생각보다 괜찮은 퀄리티의 프로젝트가 완성됐습니다. 이런 부분들은 앞으로도 유지하면 좋겠다고 생각합니다.
  • 프론트엔드 프로젝트를 쉽게 배포하는법까지 강의에 나와있어서 쉽게 따라할 수 있었습니다. 처음으로 vercel에 배포해보면서 하나의 프로젝트를 완성했다는 뿌듯함도 느낄 수 있었습니다. 배포라는 것에 대한 막연한 두려움?같은게 있었는데 그걸 허물수 있는 좋은 계기가 되었던 것 같습니다.
  • 디바운스나 낙관적업데이트를 실제로 써보면서 서비스 흐름이 유연해지는걸 느낄 수 있었고, 앞으로 다른 프로젝트를 하면서도 쓸 수 있을 것 같은 자신감이 생겼습니다.
  • React Redux처럼 상태관리를 하면서 개발해볼까 했지만 중간에 기능구현을 먼저 하자 라는 생각으로 포기한 것이 아쉽습니다. redux를 써보지 않아서 이해하는데에 시간이 많이 걸렸습니다. 이 부분은 꼭 다시 공부해야겠다고 생각했습니다.

 

Problem (아쉽게 느끼거나 앞으로 개선이 필요한 부분)

  • 처음엔 개발하면서 생겼던 문제나 고민들 하나하나를 노션에 기록하면서 진행하려고 했지만, 생각보다 어려움을 느꼈습니다. 프로젝트 초반엔 짧게나마 기록하면서 진행했지만 프로젝트 후반쯤엔 데드라인안으로 만들어야된다는 생각때문에 개발에만 시간을 쏟은 것이 아쉬움으로 남습니다.
  • 너무 명령형 프로그래밍으로 코드를 짜서 재사용성이 낮다는 점이 아쉽습니다. 지금 코드에선 UI를 조금만 수정해도 바꿔야 할 부분들이 많기 때문에 유지보수하기 힘들 것 같다는 생각이 듭니다. 이런 부분은 리팩터링을 하면서 고칠 수 있는 부분인 것 같습니다.
  • 주석을 상세히 달지 못한 점이 아쉽습니다. 협업이 아닌 개인 프로젝트임에도 불구하고 프로젝트 후반쯤엔 제 코드를 제가 이해해야되는 상황이 발생했고 그 부분에서도 시간을 많이 낭비했던 것 같습니다. 그런 과정을 겪으면서 주석의 중요성을 알 수 있는 시간이었던 것 같습니다.

 

Try (Problem에 대한 해결책, 당장 시도해 볼 부분)

  • 리팩터링을 우선적으로 해야겠다고 생각합니다. 현재 코드에선 중복된 코드들도 몇몇 있어서 그런 부분들을 고치고, 어느 역할을 하는지 주석도 상세히 적겠습니다.
  • ES6문법을 자세히 공부해야할 것 같습니다.
  • 앞으로 만들면 좋을 기능들
    • [ ] 하위페이지가 많아질 것을 대비해서 리스트 사이즈 조정 (마우스로 resize하기)
    • [ ] 글 수정할 때 수정 성공인지 실패인지 사용자에게 알려주기
    • [ ] 리치 에디터 구현해보기

 

느낀점

오직 자바스크립트만으로 SPA 노션 클론을 어떻게 해야되나 처음엔 막막했었다. 하지만 강의를 들으면서 하나씩 개념을 익히고 그것들을 또 직접 사용해보면서 결국엔 꽤 괜찮은 퀄리티로 프로젝트를 완성시킬 수 있었고, 성장한 듯한 느낌을 받을 수 있었다.

프로젝트 제출기한 막바지에는 이 기능만큼은 완성하리라 라는 생각으로 새벽 4시까지 코딩하고 그랬다..

최근들어서 이렇게 열심히 코딩한적이 있었나 되돌아보는 계기도 되었고 이렇게 잠 줄여가면서 했는데 이상하게 고통스럽지 않고 즐거웠다. (왜지...🤔)

또 배포까지 하면서 하나의 프로젝트를 완성했다는 뿌듯함도 느끼고 팀원들의 칭찬도 들어서 기분이 좋았다.

팀원들과 멘토님이 피드백해준 코드리뷰를 보면서 부족한 점을 하나씩 코드에 반영해보고 앞으로 만들면 좋을 기능들도 추가하면서 더 완성도 있게 만들고 싶은 마음이다.