- Published on
Vanilla JS Notion Cloning 프로젝트 회고
- Authors
- Name
- Hyejin Yang
데브코스를 시작하고 첫 개인 프로젝트를 진행했다.
Vanilla JS 를 사용하여 Notion 을 클론 코딩하는 것이다.
Vercel 을 이용하여 배포도 해 보았다. 배포링크
코드리뷰 과정은 여기서 확인할 수 있다.
과제의 기본 코드 구조는 강의에서 거의 대부분 알려주기 때문에, 강의 코드를 기반으로 하여 프로젝트를 진행해도 괜찮았다. 데브코스를 통해 프론트엔드를 처음 배우는 사람들에 대한 배려가 느껴졌다. 가벼운(?) 마음으로 기본 요구사항만 충족하는 과제를 하려 했다면 어렵지 않은 과제였을 것 같다는 생각이 들었다.
하지만, 모름지기 개발자라면…! 내 개발 실력을 한계까지 몰아붙여 프로젝트를 해보고 싶기 마련이다. 보너스 요구사항 중 가장 어려워보였던 “contentEditable 을 사용한 Rich 한 에디터”를 만들어보자고 다짐했다.
회고 방법론 중 하나인 4L 회고를 사용하여 회고해보았다.
Liked: 좋았던 점
1. 황준일 멘토님의 core Component 코드에 내 생각을 담아 프로젝트에 맞게 변경하여 사용한 것.
황준일 멘토님이 작성하신 Vanilla Javascript로 웹 컴포넌트 만들기 포스팅을 참고해서 Component class 를 만들고, 모든 컴포넌트들이 상속받도록 했다. 아래는 황준일 멘토님의 포스팅을 참고하여 만들었던 core Component class 이다.
export default class Component {
constructor($target, props) {
this.$target = $target;
this.props = props;
this.setup();
this.setEvent();
this.render();
this.fetchData();
}
setup() {}
mount() {}
template() {
return '';
}
render() {
this.$target.innerHTML = this.template();
this.mount();
}
setEvent() {}
setState(newState) {
this.state = { ...this.state, ...newState };
this.render();
}
addEvent(eventType, selector, callback) {
this.$target.addEventListener(eventType, (event) => {
if (!event.target.closest(selector)) return;
callback(event);
});
}
fetchData() {}
}
그러나, 황준일 멘토님의 Component
방식은 부모 컴포넌트의 상태가 변경되면, 상태가 변경되지 않은 모든 자식 컴포넌트까지도 재 랜더링된다는 문제가 있었다. 황준일 멘토님의 Component 는 render()
호출 이후, mount()
라는 함수에서 자식 컴포넌트를 다시 초기화하고 만들어주는 방식이기 때문이다.
자식 컴포넌트 쪽에서 재 랜더링을 막기 위해 setState
에서 기존 state 와 새로운 state 를 비교하는 방어 코드를 작성하는 방법은 불가능했다. mount
함수는 자식 컴포넌트를 re-render 해주는 게 아니라 new 를 사용하여 새로 만들어 버리기 때문이다.
이 문제를 해결하기 위해 initComponent()
와 initChildComponent()
라는 이름의 함수를 만들어, 컴포넌트의 DOM 요소를 컴포넌트 초기화 시에 만들어두고, render()
함수는 만들어진 DOM 요소를 조작하는 정도의 일만 수행하도록 수정하여 사용했다.
다음은 내가 프로젝트에 사용한 Component 클래스 코드이다.
export default class Component {
constructor($target, props) {
this.$target = $target;
this.props = props;
this.setup();
this.initComponent();
this.initChildComponents();
this.setEvent();
this.render();
}
setup() {}
initComponent() {}
initChildComponents() {}
setEvent() {}
render() {}
setState(newState) {
this.state = {
...this.state,
...newState,
};
this.render();
}
}
사실 이 방법이 효율적인지는 잘 모르겠다. 하지만 다른 사람의 코드를 생각없이 사용하지 않고, 내 상황에 맞게 내 생각을 담아 변경하여 사용하고자 했던 시도는 좋았던 것 같다.
2. 개발 환경 구성을 열심히 한 점
- ESLint & Prettier, Webpack, lint-staged, husky 등을 사용해 프로젝트를 세팅했다.
- Vanilla JS 로 개발하는 것에 최대한 집중하기 위해 개발 편의성을 위한 라이브러리는 사용하지 않았다.
- 코드로 바로 넘어가지 않고 프로젝트 세팅에 공을 들이느라 프로젝트 세팅에 시간이 조금 걸렸지만, 이 때 해놓은 설정으로 인해 자잘한 코드 컨벤션이나 빌드 옵션 등에 신경쓰지 않고 개발을 할 수 있어 좋았다.
Lacked: 아쉬웠던 점
1. UI 로직과 비즈니스 로직의 계층분리를 하지 못했던 것
- 팀원들과 오프라인에서 만나 네트워킹을 하느라, 노션 클론 코딩 과제를 조금 늦게 시작했다.
- 시간이 촉박하다고 느껴, 기능구현에만 집중하느라 처음부터 계층분리를 하지 못했다. 새로운 state 를 API 에서 가져오고, 이것을 자식 컴포넌트로 내려주고 재 랜더링하는 과정에서 버그가 많이 발생했었다.
- 개발 중간에 계층을 분리하려고 시도했으나, 코드베이스가 너무 커져버린 상태여서 마감일을 맞추지 못할 것 같아 진행하지 못한 점이 아쉽다.
2. 편집기의 커서 위치 저장 방식
- contentEditable 의 내용을 새로운 내용으로 변경하면, 커서 위치정보가 날아가는 이슈가 있었다.
- 커서 위치를 caret span 으로 만들어서 삽입하는 방식으로 해결했는데, 해당 해결방식이 깔끔하지 못한 것 같아서 아쉽다.
3. 버그가 많은 contenteditable 편집기
- local 과 api 에 문서를 저장할 때는, 에디터에 div 태그로 감싸진 내용을 바로 저장하지 않고, Markdown string 으로 변환해서 저장했다. 굳이 서버에서까지 편집기에 있는 내용의 html tag 정보까지 알고 있을 필요는 없다고 생각했기 때문이다.
- 하지만, 편집기의 내용이 수정되면 html 을 Markdown 으로 stringify 해주고, Markdown string 을 다시 html 로 파싱하는 과정에서 많은 버그가 발생했다. 이 부분을 해결하지 못해서 아쉽다.
Learned: 배운 점
1. Caret, isComposing 개념
- contentEditable 로 편집기를 만들면서, caret, isComposing 개념에 대해 알게 되었다.
2. 정규 표현식 사용에 익숙해진 점
- 또한, html 을 Markdown 으로 stringify 해주고, Markdown string 을 다시 html 로 파싱하는 기능을 구현하기 위해 정규 표현식을 사용하면서 정규표현식의 그룹캡쳐 기능을 사용하는 법에 대해 알게 되었다.
Longed for: 앞으로 바라는 점
1. 코드 아키텍쳐에 대한 고민
- 이번에 계층 분리를 하지 않고 컴포넌트로만 개발을 하면서 느낀 점이 있다. 다양한 개발 방식이 있겠지만, 나는 계층 분리를 해주는 방식이 나에게 맞는 것 같다.
- 아키텍쳐를 적용하지 않고, 각 컴포넌트들이 API 를 호출하도록 개발을 해보았더니 상태 관리가 어려웠다.
2. 일정관리를 잘하자!
- 프로젝트를 조금 늦게 시작했더니, 일정이 촉박해서 내가 원하는 만큼의 기능을 완성도 있게 구현하지 못한 점이 아쉽다. 다음 프로젝트부터는 좀 더 치밀하게 일정을 계획하고 진행해야겠다는 생각이 들었다.
3. 프로젝트를 더 개선해 나가기
- 프로젝트 결과물이 아쉽게 느껴진 게 나 혼자만의 생각은 아니었던 것 같다. 우리팀 팀원들도 이번 프로젝트가 아쉽게 느껴졌다고 했다.
- 그래서 우리팀 팀원들과 함께 노션 클로닝 프로젝트를 개선해보는 스터디를 진행해보기로 했다!
- 프로젝트를 아쉬운 상태에서 끝내지 않고, 계속해서 개선해나간다면 분명 배울 점이 있을 것이라고 생각한다.