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

[2팀 송창엽] [Chapter 1-1] 프레임워크 없이 SPA 만들기 #26

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

Conversation

Songchangyeop
Copy link

@Songchangyeop Songchangyeop commented Dec 18, 2024

과제 체크포인트

기본과제

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) 이벤트 위임

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

과제 셀프회고

나는 리액트 개발자였나? 싶을 정도로 바닐라 자바스크립트로 과제를 구현하는게 쉽지 않았습니다
학습자료의 지식뭉치와 GPT가 없었다면 테스트를 통과 할 수 있었을까? 하는 의문이 들기도 합니다

잘한점

  • SPA가 어떤 식으로 라우팅을 하는지 이해를 한 것
  • 더 나은 방법이 없는지 끊임없이 생각하면서 나름 즐겁게 과제를 수행 한 것
  • 퇴근 후 매일 새벽까지 과제를 수행 한 것

아쉬운점

  • GPT에 꽤 의존 한 것
  • 어떻게 동작하는지 충분히 이해하지 못 한 상태에서 기능 구현을 위해 디자인 패턴을 코드에 삽입한 것
  • 여유가 없다는 핑계로 다른 사람들의 코드를 많이 살펴보지 못한 것

기술적 성장

이번 과제를 수행하면서 클래스에 대해 익숙하지 않아 일단 한번 해보자 라는 마음으로 클래스 기반으로 코드를 작성하게 되었는데, 좌충우돌을 겪으며 클래스에 대해 이해하는 시간이었습니다
리액트를 통해 SPA로 동작하는 웹을 개발했는데 정작 SPA가 내부적으로 어떻게 동작하는지 몰라서 반성하게 되었습니다. 이번 과제를 통해 SPA가 어떻게 동작하는지 머리로 이해하는 과정이었습니다

코드 품질

만족스러운 부분

  • 이벤트 리스너를 페이지 내부가 아닌 컴포넌트로 분리하여 등록했습니다. 초기 구현에서는 페이지 단위에서 해당 페이지 내부에서 일어나는 모든 이벤트를 제어하는 구조를 생각했었는데, 코드의 관심사를 분리하는게 좋지 않을까? 라는 생각이 들어 컴포넌트 단위로 분리하여 필요한 곳에서 이벤트리스너를 등록 해주었어요

고민

  • 위 만족스러운 부분과 이어지는 내용입니다. 사실 관심사를 분리하자는 목적으로 컴포넌트 단위에 이벤트리스너를 등록해주었는데요.
    사실 이벤트 등록은 root 엘리먼트를 각 페이지에서 하위 컴포넌트로 전달해 root에 이벤트를 위임하는 것으로 처리가 되었습니다
    이 과정에서 오히려 코드를 이해하는데 복잡성이 커지는 것은 아닌가 생각이 들었지만 트레이드오프라고 위안 삼았습니다..
    자세한 코드 내용과 질문은 리뷰 받고 싶은 내용 부분에 남기도록 하겠습니다 ! ☺️

학습 효과 분석

이벤트의 위임과 핸들링에 대한 이해를 했습니다! 왜 리액트가 최상위 엘리먼트에 이벤트를 위임해서 동적인 요소에 이벤트 처리를 할 수 있었는지 깨닫는 계기가 되었어요
그저 잘 만들어진 라이브러리를 사용하기만 했는데 내부구조를 조금이나마 이해하면서 왜 사용하는 라이브러리나 프레임워크가 어떻게 동작하는지 이해를 하는 것이 중요한지 깨달았어요
이번 과제뿐 아니라 다음주 과제 등 SPA와 리액트에 대해 깊게 이해하면서 실무에서 그저 가져다 사용만 하는게 아니라 더 넓게 생각하고 고민해 볼 수 있는 능력이 길러지지 않을까 생각합니다 😆

과제 피드백

회사 업무를 제외하고 정말 오랜만에 기술적으로 깊게 고민을 많이 하게되는 시간이었습니다.
내부적으로 어떻게 동작하는지 잘 모르고 개념적으로만 이해하고 있던 SPA를 이번 과제를 통해 깊게 학습하는 계기가 되어서 너무 좋았습니다 👍
다음 과제에서도 많은 깨달음이 있었으면 좋겠습니다 !

리뷰 받고 싶은 내용

위 코드품질 부분에서 고민이었던 부분에 대해 리뷰를 받고 싶습니다

우선 제 코드에서 이벤트를 등록하고 위임하는 과정에 대해 설명을 먼저 드리려고 합니다

//  main.ts
const loginPage = new LoginPage(document.querySelector("#root")!);
const profilePage = new ProfilePage(document.querySelector("#root")!);
const mainPage = new MainPage(document.querySelector("#root")!);

최상위에서 각 페이지별로 root 엘리먼트를 파라미터로 전달합니다.

//  profile.ts
export class ProfilePage {
  private container: HTMLElement;
  private footer: Footer;
  private header: Header;
  private userInfo: UserInfoType | null;

  constructor(container: HTMLElement) {
    this.container = container;

    this.footer = new Footer();
    this.header = new Header(this.container);
    this.userInfo = UserStore.state.userInfo;
    this.attachEventListeners();

    UserStore.addObserver({
      update: (state) => {
        this.userInfo = state.userInfo;
        this.container.innerHTML = this.render();
      },
    });
  }

  render() {
   .... 
 }

 attachEventListeners() {
    // root 엘리먼트에 이벤트 리스너 등록
    this.container.addEventListener("submit", this.handleSubmit);
  }

  handleSubmit(event: SubmitEvent) {
    event.preventDefault();

    if (
      event.target instanceof HTMLFormElement &&
      event.target.id === "profile-form"
    ) {
      const username = event.target.querySelector(
        "#username",
      ) as HTMLInputElement;

      const email = event.target.querySelector("#email") as HTMLInputElement;

      const bio = event.target.querySelector("#bio") as HTMLInputElement;

      UserStore.actions.useSetAllUserInfo({
        username: username.value,
        email: email.value,
        bio: bio.value,
      });
    }
  }

상위에서 container로 전달받은 root에 이벤트리스너를 등록하기도 하고, 하위 컴포넌트인 Header에 전달하기도 합니다
Header에서도 마찬가지로 전달받은 root에 이벤트리스너를 등록합니다.

이 과정에서 드는 고민은 관심사의 분리를 목적으로 root 엘리먼트를 하위로 전달하여 이벤트를 등록하여 처리를 했는데 오히려 코드의 복잡성을 증가시키는 이유가 되지 않을까하는 고민이 드는 것 같습니다

고민이 되는 포인트들이 몇가지 있는데요 !

  1. 오히려 이벤트 리스너를 한곳에서 등록하고 관리하는 것이 관리 포인트를 줄여주지 않을까?
  2. root를 props drilling처럼 하위에 전달하는게 과연 좋은 구조인가? (꼭 그렇게 해야만 했나?)
  3. 클릭이벤트가 여러개 중첩으로 등록 된다면 충돌이 나지 않을까?

어떻게하면 컴포넌트가 독립성을 가지는 구조를 설계할 수 있을지 고민이 드는 것 같습니다..

제가 겪은 고민들을 코치님께서는 어떻게 생각하시는지 의견이 궁금합니다 🙏

@melroh629
Copy link

페이지들을 클래스 형태로 컴포넌트를 만드신거죠..?!!
저도 클래스 쓰려고 노력은 했는데 이런식은 생각을 못했었어요..! 👍👍👍
초기에 어떤식으로 설계를 하셨는지 궁금해서 나오게 된 방법(?)인지 궁금해요 !

Copy link

@2dowon 2dowon left a comment

Choose a reason for hiding this comment

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

Class 코드에는 익숙하지 않아 저는 Class를 사용한 곳이 없었는데, 이렇게 창엽님 코드를 보면서 조금은 Class 코드에 익숙해진 것 같아요!
ts 적용할 생각도 못했고, 시간이 부족할 것 같다는 핑계로 ts 적용도 안했었는데 확실히 ts 코드가 가독성도 좋은 것 같습니다 ㅎㅎ

배울 점이 많은 코드인 것 같아 시간될 때 좀 더 자세히 보러 올게요!
과제 고생하셨습니다!

Copy link

Choose a reason for hiding this comment

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

AuthentificatedNavigation, UnauthentificatedNavigation가 결국 스타일은 같다고 생각이 들어서 component 자체가 분리되는 것보다는 데이터만 분리하는 방향은 어땠을까요??

정답은 아니지만, 저는 아래처럼 작성하는 것을 선호해서 참고차 제 코드도 전달드립니다!
image

Copy link
Author

Choose a reason for hiding this comment

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

그러네요!
위에서 각 네비게이션에 대한 정보들을 가지고 있고 그걸 map을 돌면서 각각 분기를 해주는군요 !

저도 더 좋은구조라고 생각이 드네요 말씀하신 것 처럼 같은 스타일이고 이벤트도 한곳에서 관리를 하니까요 ㅎㅎ
좋은 의견 감사합니다 👍👍👍

Comment on lines +13 to +19
this.container = container;

this.footer = new Footer();
this.header = new Header(this.container);
this.userInfo = UserStore.state.userInfo;
this.attachEventListeners();

Copy link

Choose a reason for hiding this comment

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

대부분 layout 자체를 분리해서 공통으로 사용하는 코드를 많이 봤었는데, class를 이용하니 이렇게도 할 수 있네요! 👍👍

Copy link

Choose a reason for hiding this comment

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

와 Observer 패턴을 사용해서 하셨네요! 저도 user 관련 로직에서 디자인패턴 적용할 수 있는게 있지 않을까 고민했었는데, 나중에 창엽님 코드 참고해서 리팩토링 해봐야겠어요!!

Choose a reason for hiding this comment

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

그니까여,, 옵저버 적용에 ts까지 써버린 ,, 대단해..

Choose a reason for hiding this comment

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

리액트를 보고 있는 것 같습니다ㅎㅎ 👍🏼

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