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 만들기 #46

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

Conversation

yireum
Copy link

@yireum yireum commented Dec 19, 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) 이벤트 위임

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

과제 셀프회고

발제와 함께 주신 자료들을 복사해가며 순차적으로 진행하다가 지금보다 더 나은 구조 설계가 필요해 보여 두 번정도 코드를 리셋하고 시작하면서 느낀 건 과제를 구현하기 전에 어떻게 구현할지에 대한 생각을 조금 더 깊게 하고 시작해야 뿌리부터 차근차근 단단하게 만들어갈 수 있겠다는 생각을 했습니다. 기본부터 심화까지 오류를 해결해나가는 과정이 정말 어려웠고 그만큼 재밌었어요.

기술적 성장

  • 저는 Vue.js만 사용해 봤고, Javascript로만 무언가를 구현해 본 적이 처음이었어요. 폴더 구조 설계나 config 세팅, 페이지 구현, 컴포넌트 설계를 최대한 사용해 본 프레임워크와 동일하게 해보려고 노력하면서 Javascript와 SPA에 대한 이해도가 올라간 것 같아요!
  • 테스트 코드를 사용해서 테스트를 진행하고 테스트 코드 분석하고 디버깅을 해보면서, 기대동작을 구현하기 위해 어떤식으로 코드를 풀어나갈지에 대한 해답을 조금은 찾게 되었습니다!
  • 히스토리 라우트와 해시 라우트를 직접 구현해보면서 브라우저의 History API와 URL 해시값의 작동 방식을 이해할 수 있었고, SPA에서 페이지 전환이 어떻게 이루어지는지 깊이 있게 배울 수 있었습니다.

코드 품질

  • 기본적인 부분에 충실하면서도 최대한 가독성 좋게 코드를 작성하려고 노력해봤는데,
    • 불필요한 변수와 함수를 정리하면서 간결하게 작성해보려고 했고,
    • import 구문을 깔끔하게 쓰기 위해 프레임워크를 쓰면서 봤던 바렐 패턴을 적용해 보거나
    • 가독성을 향상 시키려고 상대 경로의 복잡성을 해결하기 위해 경로 별칭을 구성했습니다.
  • 가장 리팩토링하고 싶은 부분이 있다면 준일 코치님 과제 Q&A를 들으면서, 제가 구현한 게 각 페이지와 컴포넌트 안에 관련 함수들이 모두 집중되어 있어서 관심사 분리가 되어있지 않다고 느꼈습니다. 그래서 코드를 모듈화하는 방법을 찾아보고 적용하면 코드의 재사용성과 유지보수성을 높일 수 있을 거 같아요!

학습 효과 분석

  • Javascript 기본기가 확실히 부족한 것 같아요. 과제를 구현 해 보면서 더 크게 느꼈는데 알던 것도 까먹고 모르는 것도 많고요. 과제를 하면서 다른 친구들도 비슷한 생각을 한다는 걸 알게 됐고, 코치님들 말씀대로 기본에 충실하고 많이 써보고 하다보면 늘 수 있을 거 같아요!
  • 어떤 프레임워크를 쓰던지 바탕이 되는 Javascript를 공부하면서 프레임워크의 동작 원리를 조금은 이해할 수 있게 된 거 같고, 가장 도움이 될 것은 디버깅을 어떤 방식으로 해 나갈지 직접 해보고, 다른 친구들이 하는 걸 보면서 많이 배웠습니다!

리뷰 받고 싶은 내용

  1. 과제에서 페이지 html 코드 템플릿을 문자열 리터럴로 직접 선언하여 구현했는데 템플릿을 함수로 감싸서 동적으로 구현했어야 하는 생각이 드는데 정적 템플릿으로 구현하는 것은 안 좋은 방식인가요? 동적으로 더 유연하게 처리하는 게 좋다고는 알고 있는데 과제에서 사용한 템플릿 또한 동적으로 처리하는 게 더 좋겠죠?
  2. 프로필 페이지에서 상태가 업데이트 시 리렌더링 하는 구독 로직을 작성했는데, 헤더에서도 스토어를 사용하니까 추가하려고 하니 프로필페이지에서 헤더를 임포트하니까 구독이 중복될 수 있지 않을까 하는 생각이 들었는데 보통 이럴 때는 어떤식으로 리렌더링 로직을 추가하는 걸까요?
  3. 마지막에 테스트를 진행하면서 계속 어려움이 있었던 부분이 해시라우터 구현이었는데요, 일단 해시 모드인지 아닌지 플래그를 만들어서 진행했다가 routes/index.js와 routes/hash.js로 파일을 분리하면서 main.hash.js에서는 hash 라우터를 임포트 하도록 구현했습니다! 그런데 다른 테스트는 모두 통과했는데 e2e 테스트에서는 해시라우트가 통과가 안되고 오류가 났습니다. 그래서 이것 저것 시도해보다가
    routes/index.js에는 해시모드 여부에 따라 다르게 처리하는 부분을 남겨 놓고 main.hash.js에서 main.js를 임포트 하니까 모든 테스트 전부 통과 하더라고요. 근데 오류는 해결했지만 사실 main.js랑 main.hash.js에서 다른 라우터를 임포트 하게 전부 구현을 했는데 왜 e2e 테스트에서만 통과가 안됐는지 이해가 잘 안가서 리뷰를 받아보고 싶습니다!
// routes/index.js

function createRouter(options = {}) {
  const routes = options.routes || {};

  // 해시 모드 여부 확인
  // 해시 모드와 히스토리 모드에 따라 다르게 처리
  const isHashMode = () => window.location.pathname.endsWith("hash.html");

  // 현재 경로 반환
  function getPath() {
    return isHashMode()
      ? window.location.hash.slice(1) || "/"
      : window.location.pathname;
  }
...

// main.hash.js

import "./main.js"; // main.js를 임포트 하게 수정
import { Router } from "@routes/hash";
Router.init();

export const Header = () => {
const isLogin = UserStore.getValue("isLogin");
const isHashMode = () => window.location.pathname.endsWith("hash.html");

Choose a reason for hiding this comment

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

isHashMode가 함수가 아니라면 초기에 값이 결정되니까 그때그때 감지할 수 있도록 실행을 위임한거같은데
나중에 더 복잡한 상황에서 실행을 위임할 수 있는 부분이 큰 도움이 될꺼같아요!!!!

if (e.target.id === "login-form") {
handleSubmit(e);
}
});

Choose a reason for hiding this comment

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

SPA에서 라우팅이 변경될때 😁
removeEventListener를 만들어 주는 방식으로
클린업도 고민해보시면 좋을꺼같아요!

Copy link

@pangkyu pangkyu left a comment

Choose a reason for hiding this comment

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

코드에서 함수의 역할을 분리하고 하드코딩을 줄이려고 많이 노력하신 것 같습니다. 코드보면서 놓친 부분 많이 배울 수 있었습니다. 이번 주 고생 많으셨습니다!


// 클린업 함수 반환
return () => {
window.removeEventListener("hashchange", handleRouteChange);
Copy link

Choose a reason for hiding this comment

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

저는 구현하면서 이벤트를 삭제할 생각은 못했었는데 유진님 코드 보고 찾아보니, 이벤트리스너가 많이 쌓이는 경우 성능 저하나 의도하지 않은 이펙트가 발생할 수 있다고 하네요! 다음에는 저도 적절하게 리스너 해제하는 연습을 해봐야겠습니다

path: "/",
name: "홈",
id: "home",
isShow: true,

Choose a reason for hiding this comment

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

isShow 좋네여 👍

Comment on lines +22 to +26
try {
localStorage.clear();
} catch (error) {
console.log(error);
}

Choose a reason for hiding this comment

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

clear 에서는 굳이 catch 할 필요는 없을 것 같아유

Comment on lines +12 to +22
resolve: {
alias: {
"@components": path.resolve(__dirname, "./src/components"),
"@constants": path.resolve(__dirname, "./src/constants"),
"@pages": path.resolve(__dirname, "./src/pages"),
"@routes": path.resolve(__dirname, "./src/routes"),
"@services": path.resolve(__dirname, "./src/services"),
"@stores": path.resolve(__dirname, "./src/stores"),
"@utils": path.resolve(__dirname, "./src/utils"),
},
},
Copy link

Choose a reason for hiding this comment

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

너무 깔끔하고 좋아요..✨ 컨피그 세팅에 취약한데 보구 배워갑니다 히히

Copy link

Choose a reason for hiding this comment

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

엇 요거 vite-tsconfig-paths 라는 플러그인이 비슷한 역할을 해서 추천드리구 갑니다.. 총총..

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.

6 participants