Reviews
프로젝트 시작 전에는 구현 필수 사항 외에 바닐라 자바스크립트로 컴포넌트 구조를 잘 만들고, 상태 관리를 고민해서 나중에 리액트를 이해하는데 도움이 되는 방향으로 프로젝트를 하고 싶다고 생각했다. 하지만 막상 시작하자 Rich Text Editor를 구현하다 끝이 났다. 그래도 Rich Text Editor를 시도하는 것만으로도 새로 알게 된 것이 많아 의미있는 도전이었다.
무엇보다 Notion이 막강한 편리함을 위해 얼마나 디테일하게 만든 서비스인지 생각하게 되었다. 프로젝트에서 Rich Text Editor에 대한 제한 사항이 없었기 때문에 Medium/Velog같이 버튼을 클릭하면 제목, 단락 등 요소가 생성되는 방식으로 구현할까 고민을 했었다. 하지만 이왕하는 김에 마우스로 손이 안가도 요소를 생성할 수 있는 Notion의 방식을 시도하기로 결정했다. 그러면서 내가 사용자로서 Notion을 왜 벗어날 수 없는지 다시 생각하게 되었다. 기술적인 디테일이 뒷받침하는 사용성만으로도 다른 서비스와 차별화 할 수 있고, 그것이 사용자가 어떤 서비스를 선택하는 또한 이탈을 막는 중요한 요소라는 생각이 들었다.
Key Takeaways
1. 변수 / 함수 선언을 아끼자
Closures
export function debounce(fn, time) { let timer = null; return (...args) => { timer !== null && clearTimeout(timer); timer = setTimeout(() => { fn(...args); }, time); }; } export function throttle(fn, time) { let timer = false; return (...args) => { if (timer) return; fn(...args); timer = true; setTimeout(() => { timer = false; }, time); }; }
IIFE + Recursion
render() { const { documentList, isOpen } = this.state; this.$root.innerHTML = ` <a class="home" href="/">Notion Cloning</a> <div class="root-doc"> ${(function generateDocListHTML(documents) { return ` <ul class="list"> ${documents.map(({ id, title, documents: subdocuments }) => ` <li class="item" data-id="${id}"> <div class="doc"> <div class="doc-left"> <button class="more-btn" data-isopen="${isOpen.get(id)}"> <i class="bi bi-caret-right-fill"></i> </button> <a class="link" href="/documents/${id}"> ${title === '' ? 'Untitled' : title} </a> </div> <div class="doc-right"> <button class="delete-btn"> <i class="bi bi-trash3"></i> </button> <button class="create-btn"> <i class="bi bi-plus-lg"></i> </button> </div> </div> ${isOpen.get(id) ? `<div class="subdocs"> ${generateDocListHTML(subdocuments)} </div>` : '' } </li>` ).join('')} </ul>`; })(documentList)} </div> <button class="add-btn"> <i class="bi bi-plus-lg"></i> <span>Add a page</span> </button> `; }
2. Keyboard Events
InputEvent → handles input values
input
- triggers on any value change, even those does not involue keyboard actions
KeyboardEvent → handles keyboard actions(e.g. react on ArrowUp and ArrowDown)
keydown
triggers when a key is pressed(키를 누를 때) → 키 적용 전
keyup
triggers when a key is released(키를 놓을 때) → 키 적용 후
keyboradEvent.key
- 실제로 입력되는 값(키보드 배열이나 보조키 입력 여부에 따라서 달라질 수 있음)
keyboardEvent.code
- 키보드 배열에 상관없이 키의 위치에 따라 가리키는 고유한 값 → e.g. games
keyboardEvent.repeat
떼지 않고 누르고 있어서 연속으로 입력될 때 truedeprecated
keypress
when a key that produces a character value is pressedkeyboardEvent.charCode
returns a number representing Unicode number of the keykeyboardEvent.keyCode
returns a number representing a system and implementation dependent numeriacal code identifying the unmodifed value of the pressed key1. q KeyboardEvent.key -> 'q' KeyboardEvent.code -> 'KeyQ' InputEvent.data -> 'q' 2. shift + q KeyboardEvent.key -> 'Shift' + 'Q' KeyboardEvent.code -> 'ShiftLeft' or 'ShiftRight' + 'KeyQ', InputEvent.data -> 'Q'


3. Caret 맨뒤로 이동
const selection = window.getSelection() selection.selectAllChildren(node); selection.collapseToEnd();
- Modern JavaScript Tutorial - Selection and Range