diff --git a/README.md b/README.md new file mode 100644 index 00000000..cebef1b1 --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +## 과제 체크포인트 + +### 기본과제 + +#### 1) 라우팅 구현: +- [X] History API를 사용하여 SPA 라우터 구현 + - [X] '/' (홈 페이지) + - [X] '/login' (로그인 페이지) + - [X] '/profile' (프로필 페이지) +- [X] 각 라우트에 해당하는 컴포넌트 렌더링 함수 작성 +- [ ] 네비게이션 이벤트 처리 (링크 클릭 시 페이지 전환) +- [ ] 주소가 변경되어도 새로고침이 발생하지 않아야 한다. + +#### 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) 이벤트 위임 + +- [ ] 이벤트 위임 방식으로 이벤트를 관리하고 있다. + +## 과제 셀프회고 +그동안은 코드는 고치고 짜 봤지만 프레임워크를 쓰지 않고 웹 페이지를 만드는 것에 대해서는 생각해본 적이 없었다. +document.location.href 를 조작하는 원시적인 방식 이외에도 기본 제공되는 방식을 사용해서 +프레임워크 내부의 구현 방식을 직접 해볼 수 있다는 점이 신선했다. +코테와 기존 코드 유지보수에서 벗어나서 본질을 생각해 볼 수 있는 과제였다. +web과 자바스크립트의 기본적인 지식이 부족하다는 것을 느꼈다. + +### 기술적 성장 +- 새로 학습한 개념 : 라우터, pushState +- 프레임워크 메뉴얼을 사용하지 않고도 다양한 방법으로 구현을 시도해 본 점 + +### 코드 품질 +- 심화 과제와 리팩토링을 아예 못해본 점이 아쉽다. +- svelte나 vue처럼 한 파일 안에 컴포넌트 HTML, 그에 딸린 메서드를 동시에 구현해서 유지보수가 쉬운 모양으로 만들고 싶었는데 + 이것 보다 깔끔하고 보기 쉬운 레이아웃(ex. html을 단순 String이 아닌 html 형식으로 표시하거나...)으로 변경해보았다면 좋았을 것 같다. +- + +### 학습 효과 분석 +- web페이지 이동 방식에 대해 이해. +- 주니어 실무에는 어떻게 적용할 수 있을지 잘 모르겠지만 (보통 프레임워크를 사용하고, 그 프레임워크 방식대로 구현하게 되므로) + 후에 시니어가 되면 세팅을 하면서 써볼 수 있지 않을까 생각한다. + +### 과제 피드백 +- 라우터 기본 개념을 모르는 상태에서 과제를 하려니 처음 시작을 어떻게 해야 할지 막막했습니다. +- 도움되는 자료를 많이 올려주셨는데, 오히려 너무 많아서 정말 과제에 필요한 내용이 무엇인지와 알아두면 좋을 내용이 무엇인지 구분이 잘 안되었습니다. +- + +## 리뷰 받고 싶은 내용 +- 지금은 HTML코드가 단순 String으로 되어있는데, vs code에서 html형식으로 보려면 어떻게 수정하는 게 가장 빠르고 단순할까요? +- 컴포넌트가 많아지면 어떻게 정리를 하면 좋을지. 회사에서는 vue와 svelte를 써봤는데, 컴포넌트가 많아지면 많아질수록 메서드가 사방 팔방으로 흩어지고 + 재사용이 어려워진다고 느껴졌습니다. A페이지와 B페이지에서 공용으로 사용하는 컴포넌트(C)가 있다면 A 페이지와 B 페이지에서 사용되는 각각의 메서드들을 다른 회사에서는 어떻게 정리하는 편인지? diff --git a/src/main.js b/src/main.js index 036c2a38..768b7009 100644 --- a/src/main.js +++ b/src/main.js @@ -1,241 +1,49 @@ -const MainPage = () => ` -
-
-
-

항해플러스

-
- - - -
-
- - -
- -
- -
-
- 프로필 -
-

홍길동

-

5분 전

-
-
-

오늘 날씨가 정말 좋네요. 다들 좋은 하루 보내세요!

-
- - - -
-
- -
-
- 프로필 -
-

김철수

-

15분 전

-
-
-

새로운 프로젝트를 시작했어요. 열심히 코딩 중입니다!

-
- - - -
-
- -
-
- 프로필 -
-

이영희

-

30분 전

-
-
-

오늘 점심 메뉴 추천 받습니다. 뭐가 좋을까요?

-
- - - -
-
- -
-
- 프로필 -
-

박민수

-

1시간 전

-
-
-

주말에 등산 가실 분 계신가요? 함께 가요!

-
- - - -
-
- -
-
- 프로필 -
-

정수연

-

2시간 전

-
-
-

새로 나온 영화 재미있대요. 같이 보러 갈 사람?

-
- - - -
-
-
-
- - -
-
-`; - -const ErrorPage = () => ` -
-
-

항해플러스

-

404

-

페이지를 찾을 수 없습니다

-

- 요청하신 페이지가 존재하지 않거나 이동되었을 수 있습니다. -

- - 홈으로 돌아가기 - -
-
-`; - -const LoginPage = () => ` -
-
-

항해플러스

-
-
- -
-
- -
- -
- -
-
- -
-
-
-`; - -const ProfilePage = () => ` -
-
-
-
-

항해플러스

-
- - - -
-
-

- 내 프로필 -

-
-
- - -
-
- - -
-
- - -
- -
-
-
- - -
-
-
-`; - -document.body.innerHTML = ` - ${MainPage()} - ${ProfilePage()} - ${LoginPage()} - ${ErrorPage()} -`; +import loginPage from "./pages/loginPage"; +import mainPage from "./pages/mainPage"; +import profilePage from "./pages/profilePage"; +import errorPage from "./pages/errorPage"; + +//url변경 +export function navigateTo(url) { + window.history.pushState(null, null, url); + renderPage(); +} + +//페이지 렌더링 +function renderPage() { + const route = window.location.pathname; + const rootDiv = document.getElementById("root"); // root div 가져오기 + let component; + switch (route) { + case "/": + component = mainPage(); + // rootDiv.innerHTML = MainPage(); // MainPage 렌더링 + break; + case "/login": + component = loginPage(); + break; + case "/profile": + if (!chkLogin()) { + return; + } + component = profilePage(); // ProfilePage 렌더링 + break; + default: + component = errorPage(); + } + rootDiv.innerHTML = ""; // 기존 내용을 비우기 + rootDiv.appendChild(component); // LoginPage 추가 +} + +function chkLogin() { + if (!window.localStorage.getItem("user")) { + // 로그인이 되지 않았다면 + navigateTo("/login"); + return false; + } else { + return true; + } +} + +window.addEventListener("popstate", renderPage); +window.addEventListener("load", renderPage); diff --git a/src/pages/errorPage.js b/src/pages/errorPage.js new file mode 100644 index 00000000..e53baf33 --- /dev/null +++ b/src/pages/errorPage.js @@ -0,0 +1,20 @@ +export default function errorPage() { + const container = document.createElement("div"); + container.className = + "bg-gray-100 flex items-center justify-center min-h-screen"; + + container.innerHTML = ` +
+

항해플러스

+

404

+

페이지를 찾을 수 없습니다

+

+ 요청하신 페이지가 존재하지 않거나 이동되었을 수 있습니다. +

+ + 홈으로 돌아가기 + +
+ `; + return container; +} diff --git a/src/pages/loginPage.js b/src/pages/loginPage.js new file mode 100644 index 00000000..538dccb4 --- /dev/null +++ b/src/pages/loginPage.js @@ -0,0 +1,49 @@ +import { navigateTo } from "../main"; + +export default function loginPage() { + const container = document.createElement("div"); + container.className = + "bg-gray-100 flex items-center justify-center min-h-screen"; + + container.innerHTML = ` +
+

항해플러스

+
+
+ +
+
+ +
+ +
+
+ 비밀번호를 잊으셨나요? +
+
+
+ +
+
+ `; + + const form = container.querySelector("#login-form"); + form.addEventListener("submit", function (e) { + e.preventDefault(); + + const username = document.getElementById("username").value; + + let userData = { + username: username, + email: "", + bio: "", + }; + + if (username) { + localStorage.setItem("user", JSON.stringify(userData)); + navigateTo("/"); + } + }); + + return container; +} diff --git a/src/pages/mainPage.js b/src/pages/mainPage.js new file mode 100644 index 00000000..d06dead7 --- /dev/null +++ b/src/pages/mainPage.js @@ -0,0 +1,122 @@ +export default function mainPage() { + const container = document.createElement("div"); + container.className = "bg-gray-100 min-h-screen flex justify-center"; + + container.innerHTML = ` +
+
+

항해플러스

+
+ + + +
+
+ + +
+ +
+ +
+
+ 프로필 +
+

홍길동

+

5분 전

+
+
+

오늘 날씨가 정말 좋네요. 다들 좋은 하루 보내세요!

+
+ + + +
+
+ +
+
+ 프로필 +
+

김철수

+

15분 전

+
+
+

새로운 프로젝트를 시작했어요. 열심히 코딩 중입니다!

+
+ + + +
+
+ +
+
+ 프로필 +
+

이영희

+

30분 전

+
+
+

오늘 점심 메뉴 추천 받습니다. 뭐가 좋을까요?

+
+ + + +
+
+ +
+
+ 프로필 +
+

박민수

+

1시간 전

+
+
+

주말에 등산 가실 분 계신가요? 함께 가요!

+
+ + + +
+
+ +
+
+ 프로필 +
+

정수연

+

2시간 전

+
+
+

새로 나온 영화 재미있대요. 같이 보러 갈 사람?

+
+ + + +
+
+
+
+ + +
+ `; + + container.querySelector("#logout").addEventListener("click", function (e) { + e.preventDefault(); + localStorage.removeItem("user"); + alert("로그아웃 되었습니다."); + window.location.href = "/login"; + }); + return container; +} diff --git a/src/pages/profilePage.js b/src/pages/profilePage.js new file mode 100644 index 00000000..f7a00f89 --- /dev/null +++ b/src/pages/profilePage.js @@ -0,0 +1,92 @@ +export default function profilePage() { + let userInf = JSON.parse(window.localStorage.getItem("user")); + const container = document.createElement("div"); + container.className = "bg-gray-100 min-h-screen flex justify-center"; + container.innerHTML = ` +
+
+

항해플러스

+
+ + + +
+
+

+ 내 프로필 +

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ + +
+ `; + container.querySelector("#profile-form").addEventListener("submit", (e) => { + e.preventDefault(); + let userData = { + username: document.getElementById("username").value, + email: document.getElementById("email").value, + bio: document.getElementById("bio").value, + }; + + localStorage.setItem("user", JSON.stringify(userData)); + }); + return container; +}