5월 17일 ~ 6월 4일 (3주)
이번 프로젝트의 목표는 typescript 사용법을 익히고 Global 상태 관리 도구로 Recoil 사용하는 방법을 배우는 것이었다. typescript, recoil 모두 처음 접하고, 도입하는 기술들이라 초기 학습 시간이 꽤 많이 필요했고 개발 과정에서도 알 수 없는 오류를 해결하는데 많은 시간을 할애하였다.
✅ typescript로 리액트 개발해보기
✅ Recoil로 상태 관리 해보기
과거 프로젝트에서 꼭 한 번 해보고 싶었던 OAuth 로그인을 적용해봤다. OAuth는 전혀 알지 못했던 상태였기 때문에 생활코딩 OAuth2.0 강의를 들으며 OAuth 시 각 주체들의 역할과 인증 과정에 대해 정리하고, 백엔드 팀원들과 함께 OAtuth 관련 내용을 학습하는 테크톡 시간을 가지며 팀에서 OAuth를 적용할 때 어떤 flow로 진행 해야 하는지에 대한 이야기를 나누었다.
우리 팀은 깃헙 로그인을 구현하였는데, Client Id와 Client Secret을 발급 받기 위해 redirect URL이 무엇인지 정해줘야 했었다. 그때 당시에는 유저가 로그인을 하고 돌아와야하는 페이지가 특정 페이지가 아닌 메인 페이지기 때문에, redirect URL 역시 메인 페이지 일 것이라고 생각을하고 OAuth application 등록할 때 callback URL을 메인 페이지로 설정하였다.
그 결과 유저가 깃헙 로그인을 하고 메인 페이지로 돌아왔을 때 메인 페이지 URL에는 깃헙이 제공한 인증 code가 떡하니 붙어있었다.🤦♀️ 인증 code를 유저가 확인할 수 있었기 때문에 이건 OAuth 알못인 내가 봐도 말도 안 되는 상황이었다. 그래서 redirect 용 page를 새롭게 만들었고, 해당 컴포넌트 내에서는 url 파라미터에서 code를 추출하여 OAuth 처리를 하는 백엔드 API에 요청을 보내고 전달 받은 Access Token을 Local Storage에 저장하는 것까지 마친 후 다시 main 페이지로 돌아오도록 하였다.
const getToken = async() => {
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
const token = await API.post.code(code);
localStorage.setItem("token", token);
};
useEffect(() => {
getToken();
}, []);
백엔드 쪽에서 권한 제공 서버(깃헙)에게 전달받은 Aceess Token에 유저 네임, 이름 등을 넣어서 JWT 형태로 보내줄 수 있는데 우리 팀은 일단 이름 데이터만 받아서 디코딩하였다.
const token = localStorage.getItem("token");
const menuListText = !token ? "🐹 동물의 세계로 로그인"
: (() => {
const parsedToken = token && atob(token.split('.')[1]);
const {name, login} = JSON.parse(parsedToken);
return `${name}님 안녕하세요.`;
})();
에어비앤비의 메인 페이지에는 사용자가 원하는 날짜, 요금, 인원에 맞게 검색을 할 수 있는 검색창이 있다. 이 검색창은 각 검색 필터를 클릭할 때 해당 모달창이 보이고 모달창 이 외의 영역을 클릭했을 때는 모달이 닫힌다. 각 검색 필터는 검색창의 하위 컴포넌트로 존재하게끔 설계를 했고 , 검색 필터의 on/off 여부를 저장하는 상태를 reducer로 통합 관리하도록 했다. 그러다보니 on/off 대상인 컴포넌트 외에도 검색창에 속하는 모든 컴포넌트들이 불필요하게 리렌더링 되는 문제가 생겼다. 또한 검색창, 모달창, 검색 필터 사이에 상태를 교환해야하는 상황이 자주 생겨 Context API를 사용해서 글로벌로 상태를 관리하도록 하였는데, 이 또한 context를 공유할 상태별로 context 정의를 해주지 않으면 상태를 구독하고 있는 모든 컴포넌트들이 리렌더링이 되는 문제가 생길 수 밖에 없었다. 그러던 중 이 문제를 간편하게 해결할 수 있는 recoil이라는 상태 관리 라이브러리를 알게 되었고, 이번 프로젝트에서 적용하며 보다 효율적이고 간편하게 상태를 관리하는 방법을 익힐 수 있었다.
recoil을 적용해서 각 컴포넌트의 on/off 상태를 recoil의 Atom으로 관리하고, 그 Atom을 Selector를 통해 함께 관리할 수 있도록 하였다. 그 결과 원래 의도대로 리렌더링이 필요한 컴포넌트만 리렌더링이 되도록 개선할 수 있었다.
이번 프로젝트에서 처음으로 타입스크립트를 사용해서 리액트 개발을 해봤는데, 고려 해야할 게 한 두개가 아니라 머리가 아팠다. 처음엔 타입 에러가 뜰 때마다 다 any로 다 박아 넣어서 TypeScript가 아닌 AnyScript가 아닌가? 라는 생각도 들었지만 점차 타입 관련 오류가 뜨는 빈도가 줄어드는 걸 보면서 뿌듯했다. 이번 프로젝트에서 적용해본 여러 타입들이 있지만 새로운 도전으로는 enum 타입을 꼽고 싶다. 사실 다른 언어를 공부한 사람이라면 enum이라는 개념이 익숙하겠으나 그렇지 못한 나는.. 프로그래밍에서 Enum의 역할은 무엇인지, typescript에서 enum 타입을 어떻게 적용해볼 수 있을지 고민해봐야했다. 이번 미션에서 상태값을 바꾸거나 리듀서를 사용할 때 string 값으로 action type이나 데이터 종류를 값으로 받아서 그에 맞게 상태를 변경, 혹은 적절한 값은 반환하는 switch 문
을 작성하곤 했는데 그때 action type을 Enum으로 묶어서 관리하면 좋을 것 같다는 생각이 들었다, 그래서 아래와 같은 방식으로 search 컴포넌트에서 사용되는 enum을 관리한 결과 코드를 작성할 때 search.
만 입력하면 관련 enum을 모두 볼 수 있어서 편리했다.
export enum search {
in = "CHECK_IN",
out = "CHECK_OUT",
room = "ROOM_PRICE",
guests = "GUESTS",
reset = "RESET",
}
export enum className {}