HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
👻
개발 기록
/
📝
프로젝트 노트
/
API를 이용한 게시판 만들기
API를 이용한 게시판 만들기
API를 이용한 게시판 만들기

API를 이용한 게시판 만들기

Tags
Javascript
Status
Archived
notion image

🚩 목표

Function

☑️ API 렌더링
☑️ 트리구조 카테고리
☑️ 텍스트 편집기
☑️ 자동 저장

Point

☑️ validation 작성
☑️ router 활용
간단한 프로토타입 제작
간단한 프로토타입 제작

🔎 기본 동작 원리

notion image
 
  • App.js에서 모든 컴포넌트를 관리함.
  • nav.js는 좌측 카테고리를 담당함.
  • Editor.js는 text editor를 그림.
  • 그 외 landingPage는 페이지의 시작을, utils는 API 연동 및 라우터 기능에 필요함.
 

📥 API 리스트 목록 가져오기

  • API를 직접 연동하기 전 DUMMY_DATA 를 만들어 반영이 되는지 확인 후 연동하기.
  • API 구조 : 루트 document에 하위 document가 연속되는 형태.
  • 단순 map을 활용하면 하위 document가 보이지 않는 현상 발생.
  • 하위 document를 계속 호출하는 재귀 함수 사용하기!
function renderPosts(parentPost, currentPost) { if (!currentPost) return; currentPost.map(post => { const $postElement = document.createElement('div') const { id, title, documents: nextPost } = post $postElement.dataset.id = id $postElement.textContent = title parentPost.appendChild($postElement) renderPosts($postElement, nextPost) }) }

API 연동

const fetchPosts = async () => { const posts = await request('/documents') this.setState(posts) } fetchPosts()
 

🔗 라우터 구현

🤔
SPA 환경에서 어떻게 화면 전환을 이룰까?
  • 라우터는 특정한 URL에 대해 특정한 뷰로 연결함.
  • 랜딩 페이지에서 새로운 포스트를 만드는 버튼 클릭시 SPA환경에서 URL과 페이지 전환이 이루어져야 함.

라우터 원리

  • initRouter는 CustomEvent를 등록함.
  • push를 통해 이동할 URL을 파라미터로 받아 CustomEvent를 실행시킴.
  • CustomEvent가 실행될 때, initRouter로부터 전달받은 onRoute가 실행됨.
💡
onRoute가 실행될 수 있는 이유? initRouter가 호출될 때, 전달받은 onRoute를 closer로 기억하고 있기 때문.
const ROUTE_CHANGE_EVENT_NAME = 'route-change' export const initRouter = (onRoute) => { window.addEventListener(ROUTE_CHANGE_EVENT_NAME, e => { const { nextUrl } = e.detail if (nextUrl) { history.pushState(null, null, nextUrl) onRoute() } }) } export const push = nextUrl => { window.dispatchEvent(new CustomEvent(ROUTE_CHANGE_EVENT_NAME, { detail: { nextUrl } })) }
  • CustomEvent() 생성자는 새로운 CustomEvent 를 생성함.
notion image
  • typeArg : DOMString 은 이벤트의 이름을 나타냄.
  • customEventInit (Optional) : detail로 표현하며 기본 값은 null임. 이벤트에 관련된 이벤트 의존 값.

적용하기

  • 페이지 전환이 필요한 page 돔 객체 생성.
  • initRouter로 this.route()를 실행시킴. 이때 this에 대한 실행 컨텍스트가 실행되지 않도록 () => this.route()를 파라미터로 전달함.
  • this.route는 pathname으로 그릴 컴포넌트를 결정함.
  • LandingPage.js에서 이벤트를 발생시킬 버튼을 선택 후 push에 이동할 URL 이름을 전달함. ex) push('new-post')
notion image
new post 버튼을 클릭했을 때
new post 버튼을 클릭했을 때

뒤로가기

popstate 는 사용자의 세션 기록 탐색으로 인해 현재 활성화된 기록 항목이 바뀔 때 발생함.
window.addEventListener('popstate', () => { this.route() })
 

💾 자동 저장 기능

  • 텍스트를 입력하면 자동 저장이 되어야 함.
  • onEditing 함수를 만들어 keyup 이벤트가 일어날 때 setItem이 이뤄지도록 함.
  • 포인트 1. 자동저장 이벤트가 일어날 때마다 render가 호출되어 input에 글을 쓸 수 없는 현상 발생 → editor-container가 존재할 때만 render를 그릴 수 있도록 함.
  • 포인트 2. keyup 이벤트 동작 원리 → this.state[name]이 있을 때, target.value를 포함한 상태를 nextState로 전달함.
잘 저장된다!
잘 저장된다!

debounce

  • keyup 이벤트 발생을 지연시킴.
  • DB에 저장하는 시간을 늦춤으로써 효율적으로 데이터를 전송하기 위함.
  • timer 변수를 설정하여 setTimeout으로 setItem을 1초마다 발생하도록 함. 이때 clearTimeout 으로 onEditing이 발생할 때 timer가 초기화되도록 해주어야 함.

API로 GET, POST하기

  • api에서 반환하는 자료, 필요로하는 파라미터를 파악해 해당 값 전달하기.
  • async, await 사용하기.
  • Editor.js에서 updateDocument를 이용해 api 서버에 PUT한 후, App.js에서 라우터의 경로가 일치할 때 fetchDocument를 하고 해당 값을 구조분해 하여 setState함.
 

✍️ 목록에서 문서 가져오기

  • nav에서 addEventListener로 목록을 클릭하면 해당 id 값으로 url을 push함.
  • docEditPage에서 fetchPost로 state를 그림.

깨알 포인트

  • padding-top을 주어야 자식 노드들까지 padding이 적용됨 + padding으로 선택해야 정확한 노드 선택 가능.
  • classList.add를 추천. className 은 여러 class를 설정하기 어려움.
  • render에다 이벤트를 걸면 버블링 발생으로 중복 실행됨.
  • 하위 노드를 만들 땐 재귀와 depth를 이용해 style 등의 값을 컨트롤 하기!
  • id값을 확인하는 코드는 중복되는 경우가 많으니 getId라는 함수를 만들기!
 

📑 문서 생성, 삭제

  • 새로운 문서 만들기 버튼을 누르면 POST로 id값을 전달받아 해당 id로 편집 가능한 페이지의 url을 만듦.
  • reqeust 함수에 서버에서 제공된 title, parent 값을 넣어 전달함.
  • 반환된 값 중 id를 구조분해하여 url로 push함.
  • 이를 postNewDocument 라는 함수로 만들어 새로운 문서 생성이 필요한 곳에 전달.
올바르게 생성됨.
올바르게 생성됨.

Nav 목록 생성

this.postNewDocument = async (parentId) => { const document = await postDocument(parentId); document.title = "Untitled" document.documents = document.documents ?? []; push(`/documents/${document.id}`) return document; }
  1. 부모 id를 파라미터로 받아 document를 불러옴.
  1. document에 대한 정보를 처리함.
  1. document가 가진 id로 url을 push함.
  1. document를 return함 → 반환된 값으로 setState를 이용해 새로운 상태를 그리기 위함.
🤔
- 돔 객체는 HTML의 tree node 중 하나를 말하고, 컴포넌트는 로직이 있는 UI 뭉치를 의미함. - request를 받아 새로운 상태를 그린다는 개념이 잡혀 있어야 함. - validate코드를 작성할 땐 객체인지 배열인지 구별하고 해당 형식 안에 어떤 값이 어떤 형태로 들어가 있는지 확인해야 함. - Content-Type으로 보내는 데이터가 json임을 명시해주지 않으면 서버측에서 일반적인 text문으로 받아들여 제대로 요청이 전달되지 않음.

Nav 목록 삭제

this.deleteDocument = async (id) => { await request(`/documents/${id}`, { method: "DELETE", }); push(`/`) }
  1. id를 파라미터로 받아 삭제함.
  1. root로 url을 이동함.
🤔
요소를 초기화하고 다시 그린다는 개념 잡기!

삭제, 생성에 필요한 함수 만들기

  • doc으로부터 id를 찾는 함수 : find를 이용하여 doc.id와 파라미터로 받은 id가 일치한다면 doc을 return하고, 그렇지 않은 경우 파라미터로 받은 doc을 순회하며 id를 찾음 (재귀함수).
  • 루트로부터 doc을 생성하는 함수 : doc을 파라미터로받아 setState를 함.
  • 부모로부터 doc을 생성하는 함수 : parentId, doc을 파라미터로 받아 id로 parendDoc을 찾음. 찾은 값을 document로 지정하고 해당 document에 setState를 그림.
  • 부모를 찾아 remove하는 함수 : prentId와 docId를 파라미터로 받아 부모 doc을 찾고 doc으로 지정, 페이지를 그림. delete함수로 해당 id를 지움.

상태에 대하여

상태가 필요할 때 → 객체가 자율적으로 바뀔 때, 여러가지 인스턴스들이 개별적인 상태를 필요로 할 때!
🤔
기획이 확실히 이루어져야 함. 어디서 새로운 버튼이 필요한지, 새로운 버튼은 어떤 상태를 가지고 어떤 역할을 하는지 구분짓지 않으면 코드가 완성될 수 없음.