“pjax 방식으로 라우터를 구현하기 위해서 서버와 도움이 필요하다”라는 말이 이제는 이해가 된다. ajax 방식은 url이 변경되지 않아 seo에 문제가 있어서 이를 해결하기 위해서 pushState를 통해 history API를 구현해준다.
그리고 router 함수는 path를 받아서 해당 path의 컴포넌트를 가지는 route를 찾아서 현재 페이지로 렌더링해준다.
이 때, routes 배열안의 어떤 라우터는 접근 권한이 필요한 라우터가 있는데, 이들한테는 guard라는 프로퍼티가 존재한다. 그러므로 router 함수에서도 guard라는 프로퍼티가 있을 때, 또 guard 프로퍼티의 값이 true, false냐에 따라 컴포넌트를 다르게 보여줘야하는 로직을 짜야한다.
이 개념이 CSR(Client Side Rendering)이다. 서버에서는 다음과 같이 코드를 구현해주었다.
서버에서는 모든 경로에서 get 요청이 들어오면 auth 함수의 내부의 로직을 기준으로 사용자가 JWT 토큰이 있는지 유무를 판단하여 결과를 반환한다. 이 결과에 따라 rootContainer를 응답으로 보내주고 클라이언트는 이를 받아서 조건에 맞게미리 구현해둔 router 함수로 화면에 렌더링을 해준다.
그리고 서버에서 또 다른 함수로 JWT 토큰 유무에 따라 성공, 실패 결과를 전달해줘야하므로 auth 와 별개인 isSignin() 함수를 만들어줬다.
우리는 이 부분에서 auth 라는 함수 하나로만 해결하려고 하다 보니 res.send로 오류를 보내면 /rank URL로 직접 요청시 오류 페이지가 보이게 되고 redirect 를 해주자니 오류가 안보내져서 클라이언트에서 router 함수 로직을 구현할 수 없는 딜레마를 겪었다.
그 결과 클라이언트의 isSigned 비동기 함수가 axios로 get 요청을 보내서 data(boolean)를 반환해준 값을 guard 프로퍼티에 등록해주었다.
이를 사용해 router 함수에서 로직을 구현해주었다.
소감
오늘은 월요일이라서 그런지 머리도 잘 안돌아가고 주말에 푹 쉬어서 죄책감이 약간 들어서 자존감도 떨어지는 하루였다.
그래도 늘 이런 생각을 하면 달라지는 것은 없다. 이런 생각을 할 시간에 좀 더 연구하고 공부하는 게 나의 미래에 더 도움이 된다.
이제 미니 프로젝트 마감이 이번주 금요일로 다가왔으니 앞으로 며칠만 더 힘내서 열심히 공부하고 배운것을 꼼꼼하게 정리하도록 해야겠다.
현재 CBD의 구조 상 이벤트를 component를 호출할 때마다 이벤트 목록을 확인하여 이미 등록된 이벤트는 중복 등록되지 않도록 하고 있다.
그래서 효율적이라고 생각했다. 하지만 하위 컴포넌트에서 이벤트를 등록해주고 이벤트 핸들러 안에서 this.prop로 상태를 참조할 때, 이벤트가 등록될 시점을 참조하고 있어서 setState로 전역 상태를 변경시켜줘도 변경된 state를 이벤트 핸들러 내부에서 참조할 수 없는 문제점이 발생하였다.
이러한 문제점을 해결하기 위해 구조를 바꿔보려는 시도를 하였지만, 프로젝트 시간 상 이는 감당하기 어려워 포기하였다.
컴포넌트 호출 시마다 이벤트 제거와 새로 등록
위 문제를 해결하기 위한 방법으로 컴포넌트를 호출할 때마다 등록된 이벤트를 제거하고 새로 이벤트를 등록하는 방법이 있다.
하지만 이벤트 핸들러를 **익명함수(() => {})**를 통해 등록해주고 있었기 때문에 이를 전부 기명함수로 변경해줘야지만 removeEventListener() 메서드를 사용하여 등록된 이벤트를 제거할 수 있었다.
하지만, 기명함수로 바꿔서 addEventListener() 메서드로 등록을 해줘도 제대로 동작하지 않았고 이부분에서 많은 시간을 할애하였다. 지금 내가 해결하려는 문제가 작은 부분에 몰두해있는 것 같고 시간이 부족하여 방법을 바꾸기로 하였다.
그래서 현 구조에서 하위 컴포넌트의 상태를 관리해주기 위해 App 컴포넌트에 상태를 등록하여 전역 상태로 관리해주기로 결정하였다.
전역 상태를 domStr()을 호출할 때 지역 변수에 할당하여 지역 변수를 가지고 이벤트 핸들러 내부에서 로직을 구현하였다.
이로 인한 문제점이 게임의 진행을 위해서는 렌더링을 해주기 위해서는 전역 상태를 setState로 변경해줘야 하고 지역 변수를 변경도 이벤트 핸들러 내부에서 따로 해줘야 하므로 같은 로직을 구현하는 상태를 2번 관리해줘야 하는 불편함이 생겼다.
이는 다음주에 리팩토링이 필요할 것으로 보인다.
2. 로그인 기능 및 서버 구현
클라이언트 사이드
먼저 클라이언트는 로그인 페이지에서 input 값으로 요청을 보낸다. 이 때, 서버에 대한 요청을 e.preventDefault()로 막아주고 payload에다가 input 값을 담아서 axios로 post 요청을 보내주었다.(서버의 응답이 도착할 때 까지 await해주었다.)
auth 함수에서는 요청의 header나 쿠키에 권한이나 access token이 있는지를 확인하여 이를 해석하고 로그인 성공 실패 유무를 판단하여 응답을 보내준다.
소감
프로젝트를 시작하기 전에 기술적인 기획이 매우 중요하다는 것을 깨닫게 되었다. 하위 컴포넌트에서 지역 상태를 관리할 수 없을 것이라는 것을 구조를 보고 알 수 있었더라면, 그에 따라 구조를 개선한 후 프로젝트에 임할 수 있었을 텐데 중간에 큰 문제에 봉착하게 되니 머리가 텅 비워지는 상황을 경험하였다. 결국 시간을 고려하여 회피를 하게되는 문제점이 발생하게 되었다.
팀원 중 한명이 몸이 안좋아 2명이서 프로젝트를 진행하게 되었다. 3명이서 같이 나눠서 공부하고 공유하면 더욱 좋았을텐데 이점은 조금 아쉽다. 건강상의 이유로 참여하지 못한 팀원도 안쓰러웠다. 다음주에 쾌유해서 돌아오면 그동안 공부했던 것을 공유해주면서 배웠던 지식을 다시 정리해봐야겠다.
CBD 라이브러리에서 컴포넌트가 바뀌었을 때, 라우터와 렌더링을 구현해주었다. 각 컴포넌트에서 click 이벤트가 발생하면 그 때의 a 태그의 href 값을 구하여 pushState 메서드로 history를 등록해주고 상태로도 할당해주어 렌더링을 발생시켰다.
즉, signin 클릭해서 click 이벤트 발생하면 history등록, setState로 상태 갱신을 해주었다.
그리고 App 컴포넌트에서는 popstate 이벤트를 등록하여 브라우저의 뒤로가기, 다음 버튼을 클릭했을 때, 해당 path 상태를 window.location.pathname으로 변경시켜줘서 라우터가 변경되면 렌더링 발생하는 기능도 구현하였다.
문제점은 구현을 하다가 작은 부분에서 막혀서 그 작은 부분을 해결하려고 몰두해있었던 점이 문제라고 생각한다. 작은 문제는 근본적인 문제가 아닌 경우가 많은 것 같다. 큰 문제를 해결하면 작은 문제는 알아서 해결될 수 있으니 큰 틀에서 생각하려고 해야겠다.
1. click 이벤트 발생 시 -> pushState 메서드로 history 관리 + setState 메서드로 화면 재렌더링 발생
2. history API 사용 시 -> popstate 이벤트 핸들러 등록하여 setState 메서드로 path 상태를 window.location.pathname로 갱신
popState 시 navigate() 함수 안에서 pushState() 해주고 있어서 더 이상 history API가 제대로 동작하지 않으므로.. 이를 확인하자.
위 두가지 로직이 전부이다.
2. MatchingCards CBD 상태 관리
매칭 카드 알고리즘 구현한 것을 CBD 구조에 맞게 상태로 관리해주었다. CBD 구조가 setState로 상태가 변경되서 렌더링 될 때, App 컴포넌트는 한번만 호출되지만, App 컴포넌트에서 다른 컴포넌트를 그려주는 역할을 하는 domStr() 메서드가 반복 호출 되므로, 이 때, 다른 컴포넌트(클래스)가 new 연산자로 호출되므로 컴포넌트가 새롭게 생성되어 지역 컴포넌트의 상태를 관리해주기가 까다로웠다.
이러한 문제를 해결하기 위해서는 CBD 구조를 개편해야만 했다. 하지만 프로젝트 시간동안에는 힘들것 같아서 상태관리를 App에서만 해주기로 하였다.
라우터를 구현해주기 위해서 a태그의 href 속성값을 가져와서 path에 할당하고 window.history에 pushState 메서드를 사용하여 history 등록해주었다. history로 등록을 해줘야 브라우저 다음, 이전 버튼이 제대로 동작한다.
위 방식은 pjax 방식으로 클릭 이벤트를 캐치하고 preventDefault로 서버로 요청을 방지한다. 이후 href 속성의 path를 사용하여 ajax 요청을 하는 방식이다.
ajax 요청은 브라우저의 url을 변경하지 않아 history 관리가 불가하기 때문에 이 때, pushState 메서드를 사용하여 URL을 변경하고 URL을 history entry로 추가하지만 서버로 HTTP 요청을 보내지는 않는다.
예제를 보면서 pjax 방식을 따라서 구현해봤는데, 우리 팀의 CBD Library 방식에는 맞지 않게 코드를 구현하였다.
문제1. 상태 관리 미흡
이벤트가 발생하여 컴포넌트가 대체되어 화면의 렌더링이 발생하게 되는데, 이를 상태로 관리하지 않고 custom 이벤트를 생성하여 dispatch하여 App 컴포넌트에서 custom 이벤트 리스너를 등록하고 또 이걸 보여주기 위해서 render 함수 있는 곳에서 custom 이벤트 리스너를 등록하는 복잡한 구조로 하였다. 그 결과 각 커스텀 이벤트 마다 render를 호출해주고 있으므로 CBD Library를 사용한 의미가 사라졌다.
이를 해결하기 위해서 현재 내가 있는 페이지를 상태로 관리하는 것으로 코드를 개선하였다.
2. 서버 오픈 시 고려해야할 점
서버 연결 시 server.js에 연결을 하는데 이 때는 node.js 환경이므로, window 객체가 존재 하지 않는다.
commonJs 방식으로 서버를 구현했다면 package.json 파일에 type=”module”을 추가해줘야하지만 require 방식으로 구현했다면, type=”module”을 빼줘야 한다.
소감
오늘 혼자 힘으로 라우터에 대해 공부하여 프로젝트에 적용해보며 라우터가 어떻게 동작하는지 알게되었다. 혼자 힘으로 해보니깐 CBD Library 가 어떤 구조로 작동하고 이 구조를 맞춰서 내가 라우터를 어떻게 구현하면 되는지 생각할 수 있어서 인상깊었다.
채련님과 로그인, 회원가입 구현을 위해 서버에 대해서 공부도 하였는데, 내일 좀 더 깊게 공부해봐야겠다. 아직 우리 구조에서 서버와 어떻게 통신을 해야하는지 제대로 이해가 되지 않는다.
지윤님이 만드신 카드 게임 알고리즘에서 카드 2장이 뒤집히기 전에 새로운 카드를 눌렀을 때, 3장의 카드가 뒤집히는 문제점을 같이 해결하였다.