Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[13팀 김도은] [Chapter 1-1] 프레임워크 없이 SPA 만들기 #15

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

dosilv
Copy link

@dosilv dosilv commented Dec 17, 2024

Chapter.1-1.mov

과제 체크포인트

기본과제

1) 라우팅 구현:

  • History API를 사용하여 SPA 라우터 구현
    • '/' (홈 페이지)
    • '/login' (로그인 페이지)
    • '/profile' (프로필 페이지)
  • 각 라우트에 해당하는 컴포넌트 렌더링 함수 작성
  • 네비게이션 이벤트 처리 (링크 클릭 시 페이지 전환)
  • 주소가 변경되어도 새로고침이 발생하지 않아야 한다.

2) 사용자 관리 기능:

  • LocalStorage를 사용한 간단한 사용자 데이터 관리
    • 사용자 정보 저장 (이름, 간단한 소개)
    • 로그인 상태 관리 (로그인/로그아웃 토글)
  • 로그인 폼 구현
    • 사용자 이름 입력 및 검증
    • 로그인 버튼 클릭 시 LocalStorage에 사용자 정보 저장
  • 로그아웃 기능 구현
    • 로그아웃 버튼 클릭 시 LocalStorage에서 사용자 정보 제거

3) 프로필 페이지 구현:

  • 현재 로그인한 사용자의 정보 표시
    • 사용자 이름
    • 간단한 소개
  • 프로필 수정 기능
    • 사용자 소개 텍스트 수정 가능
    • 수정된 정보 LocalStorage에 저장

4) 컴포넌트 기반 구조 설계:

  • 재사용 가능한 컴포넌트 작성
    • Header 컴포넌트
    • Footer 컴포넌트
  • 페이지별 컴포넌트 작성
    • HomePage 컴포넌트
    • ProfilePage 컴포넌트
    • NotFoundPage 컴포넌트

5) 상태 관리 초기 구현:

  • 간단한 상태 관리 시스템 설계
    • 전역 상태 객체 생성 (예: 현재 로그인한 사용자 정보)
  • 상태 변경 함수 구현
    • 상태 업데이트 시 관련 컴포넌트 리렌더링

6) 이벤트 처리 및 DOM 조작:

  • 사용자 입력 처리 (로그인 폼, 프로필 수정 등)
  • 동적 컨텐츠 렌더링 (사용자 정보 표시, 페이지 전환 등)

7) 라우팅 예외 처리:

  • 잘못된 라우트 접근 시 404 페이지 표시

심화과제

1) 해시 라우터 구현

  • location.hash를 이용하여 SPA 라우터 구현
    • '/#/' (홈 페이지)
    • '/#/login' (로그인 페이지)
    • '/#/profile' (프로필 페이지)

2) 라우트 가드 구현

  • 로그인 상태에 따른 접근 제어
  • 비로그인 사용자의 특정 페이지 접근 시 로그인 페이지로 리다이렉션

3) 이벤트 위임

  • 이벤트 위임 방식으로 이벤트를 관리하고 있다.

과제 셀프회고

기술적 성장

웹 내장 이벤트 학습

  1. 라우터를 구현하며 load, popstate, hashchange와 같은 웹 브라우저 내장 이벤트들에 대해 공부할 수 있었습니다.

  2. 실무에서는 흔히 click, submit, mouseover 등과 같은 사용자 인터페이스 관련 이벤트들만 자주 사용해왔는데, 이번에 과제를 하며 라우팅, 렌더링 등의 작업에 리액트가 많은 부분을 자동화해 주고 있는 덕분에 1번에서 언급한 이벤트들에는 크게 신경을 쓰지 않아도 되었다는 사실을 깨달았습니다.

  3. 특히나 비슷해 보이는 loadDOMContentLoaded 이벤트의 차이점에 대해 알게 되었습니다.

  • load: 페이지에 포함된 모든 리소스(HTML, CSS, JS, 이미지 등)가 완전히 로드된 후 발생
  • DOMContentLoaded: HTML 문서 파싱 후, DOM 구조가 준비되면 발생 (CSS와 스크립트의 로드 완료 여부와 무관)
    이미지 등의 다른 리소스를 기반으로 작업이 필요한 경우를 제외하고는, DOM이 준비되면 처리 가능한� 일들은 DOMContentLoaded 이벤트를 트리거로 실행해도 무방함! react hook으로 따지면 load는 useLayoutEffect, DOMContetnLoaded는 useEffect와 유사하다고 느낌
  1. 오프더레코드 바보짓... 무엇이 잘못되었을까요?
Screenshot 2024-12-18 at 3 47 22 AM

hash router 구현 과정에서 직접 클릭하는 인터랙션으로는 라우팅이 잘 동작하는데, 테스트 코드가 도저히 통과되지 않아 한참을 고심했습니다. 화면을 공유해 팀원분과 같이 살펴보면서 결국 문제를 찾았는데, hashchange를 hashChange로 작성하는 바람에... 이벤트 등록이 제대로 되지 않았던 거였어요. 허무했지만 이것 또한 배움의 과정~~🥲

옵저버 패턴을 이용한 전역 상태 관리 구현

  1. 기본, 심화 과제를 끝내고 욕심이 나서 옵저버 패턴을 이용해 전역 상태 관리 스토어를 구현했습니다.
  2. 전역 상태이기 때문에 싱글톤 기반으로 만들었는데, 평소 클래스로 싱글톤 패턴은 많이 사용해봤으니까 함수로는 할 수 없을까? 클로저로 어떻게 어떻게 해보면 되지 않을까? 하는 생각이 들었어요. 많은 고민과 구글링과 챗GPT를 괴롭힌 끝에... 클로저와 함께 즉시 실행 함수(IIFE, Immediately Invoked Function Expression)라는 개념을 이용해서 구현할 수 있었습니다.

코드 품질

  1. 일단 라이브러리 없이 라우팅과 상태관리를 잘 돌아가도록 구현했다는 것만으로도 개인적으로 큰 배움이었고 만족스러웠습니다.
  2. 하지만 상태관리 쪽은 시간에 쫓기듯 진행하다 보니 너무 추상적이기도 하고, 가독성 측면에서 정돈되지 않은 느낌이 많이 들어(특히 MainPage init 쪽) 아쉽기도 합니다..!
  3. 라우터 설계와 관련해서 다른 분들의 코드를 구경하다 보니, 내 라우터를 너무 단순하게 짰나...? 하는 생각이 문득 들었어요. 복잡한 라우팅 관리를 위해서는 더 체계적인 구조가 필요할 것 같은데, 항상 React router dom에 의존해왔다 보니 혼자서는 잘 떠오르지가 않더라구요.

학습 효과 분석

  1. 사실 실무 프로덕트에서 해시라우터를 사용하고 있는데(히스토리를 알 수 없는 오래전부터..), 팀원들과 매번 시간 될 때 제거하자고 말하면서도 정확한 개념이나 문제점을 잘 모르고 있었어요. 이번에 과제를 하며 찾아보다가 해시라우터가 SEO에도 좋지 않다는 걸 알게 되었고, 라우터를 직접 구현하며 자신감도 얻었으니 이젠 정말 리팩토링을 해야겠다! 하는 다짐을 했습니다.
  2. 상태관리 스토어 생성 시 적용한 즉시 실행 함수(IIFE)라는 개념이 처음엔 생소해서 신기해 하며 공부했는데, 함수형으로 코딩을 하면서도 변수를 유지하거나 캡슐화를 할 수 있어서 잘 활용하면 무척 유용할 것 같다고 느꼈습니다. 까먹기 전에 실무에서 기회가 되면 적용해 보는 걸로..! 💪🏻

과제 피드백

  • 한마디로 요약하자면 처음엔 '메모장에 코딩하는 느낌...!'이었습니다 🫠
  • 프레임워크의 도움 없이 vanilla JS만으로 앱을 구현해 본 게 너무 오랜만이라 낯설었지만, DOM과 url을 직접 하나하나 조작하며 놀다 보니 웹의 동작 원리를 심도있게 고민하게 되어 재밌으면서도.. 이런 걸 알아서 척척 해주는 리액트의 소중함을 깨닫게 되었어요.
  • 그동안 편한 라이브러리만 찾으며 소홀히 해왔던 기초를 탄탄히 다질 수 있는 기회를 가져 좋았습니다.

리뷰 받고 싶은 내용

  1. 처음에는 history routes와 hash routes를 별도로 작성했다가, hash router에서도 결국 #만 제거하면 그 외의 path는 동일하기 때문에 하나의 라우트 객체를 공용으로 사용하도록 구현했습니다. 동작상으론 이상이 없지만, 혹시 좋지 못한 방법이거나 예상치 못한 사이드 이펙트가 있을지 궁금합니다.
  2. 라우트 가드를 구현할 때 가장 먼저 떠오른 것이 인터셉터라 그렇게 구현을 했는데, 이 방식에 문제는 없는지, 혹은 더 일반적인 방법이 있는지 궁금합니다. 그리고 404 페이지를 띄울 때에도 처음에는 인터셉터에서 path를 404로 반환하려다가, 그렇게 해 봤더니 입력한 path가 남지 않고 무조건 404로 변경되는 게 좋지 않은 것 같아 null 체크를 이용하는 방식으로 변경했습니다. (const page = Routes[pathToGo] ?? Routes[404];)그치만 이 방법 또한 마음에 들지 않아서 더 나은 아이디어가 있다면 힌트를 주시면 감사하겠습니다!
  3. 개념만 알고 있던 FSD 폴더구조를 이번에 처음 시도해보았는데, 혹시 구조상 뭔가 잘못 사용했다거나 '이 파일은 이 디렉토리가 더 적절하다'와 같은 피드백이 있다면 부탁드려요!
  4. 상태관리 Store를 구현하며 옵저빙을 위해 listener를 등록하도록 했는데, subscribe 함수가 해당 구독을 취소하는 함수를 반환하기는 하지만 현재 구조에서는 각 페이지의 생명주기가 모호해서 해당 함수를 어디서 불러야 할지 모르겠더라구요. 그래서 메인 페이지가 init될 때마다 리스너가 늘어나는 기현상이 발생해서 결국 차선책으로 clearListener 함수를 추가해 모든 구독을 끊어버렸는데 보다 나은 방법은 뭐가 있을지 조언을 구하고 싶습니다..!
  5. 이것저것 하다 보니 재밌어서 인수조건에 없는 부가 기능들을 마구 집어넣었는데, 막상 제출한 코드를 보니까 요구 사항이었던 라우트 기능에 대한 코드보다 그 외의 코드들이 더 많아져서.. 혹시 다음 과제부터는 ✨출제자의 의도✨에 더 집중해 달라는 피드백을 주셔도 수용하겠습니당 😗

@dosilv dosilv changed the title [13팀 김도] [Chapter 1-1] 프레임워크 없이 SPA 만들기 [13팀 김도은] [Chapter 1-1] 프레임워크 없이 SPA 만들기 Dec 17, 2024

<div class="space-y-4" id="post-area">

${[...postList]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메인 페이지에 map 함수로 목록을 구현하신 부분 정말 효율적인 방식인 것 같아요! 🥰

const store = Store.getInstance();

export const historyRouter = (path) => {
store.clearListeners();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

라우터에 클린업 함수를 추가하셨네요! 이렇게 하면 불필요한 이벤트 리스너가 남지 않아 성능 개선이 도움이 될 거 같아요. 저도 참고하겠습니다👍🏻

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사실 지금으로선 괜찮지만, 라우트를 변경해도 유지되어야 하는 subscriber가 있을 경우를 고려해서 다른 방식을 찾고 싶었는데... 현재 구조에선 찾지 못해서 이렇게 구현했어요ㅎ.ㅎ

nav.addEventListener("click", (e) => {
e.preventDefault();

if (e.target.innerHTML === LOGOUT) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

innerHTML 는 "로그아웃" 이라는 문자열 대신 다른 내용(이를테면 단순 문자열이 아니라 dom logout 과 같은,,)으로 내용이 바뀌면 로직이 동작하지 않을 것 같습니다. 아마도 불변할 id 값을 기준으로 동작시키면 변경에 더 안전할 것 같아요

Suggested change
if (e.target.innerHTML === LOGOUT) {
if (e.target.id === LOGOUT) {

Copy link

@creco-hanghae creco-hanghae left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

옵저버 패턴, 싱글톤, fsd 구조 등등 뿐만 아니라 서비스 로직도 요구사항을 넘어서서 완성도가 높은 것 같습니다 고생하셨어요 🎉

@sthgml
Copy link

sthgml commented Dec 22, 2024

코드 넘 깔끔한 거 같아용 🥹 멋있슴다

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants