1. 들어가며
드림코딩 리액트 강의의 마지막 섹션이자, 이전에 진행하였던 유튜브 클론코딩과 이어지는 프로젝트이다. 유튜브 프로젝트에서는 외부 API나 기본적인 리액트의 구조를 익힐 수 있었다면, 본 프로젝트에서는 Firebase를 통한 DB, 인증, 클라우딩까지 영역을 확장하였다.
전체적인 스타일링보다는, 단계적으로 개발이 진행되는 과정과 기능들을 어떤 로직으로, 왜 그렇게 구현했는지를 이해하며 따라가려고 노력했다.
2. 기능
구현해야할 주요 기능은 크게 네 가지로 나뉘어진다. 디테일한 로직이나 코드는 생략하겠음.
- 로고, 로그인, 장바구니 갯수 등의 정보를 담고 있는 헤더
- 상품 리스트를 확인할 수 있는 메인페이지
- 제품들의 상세 정보와 장바구니에 추가할 수 있는 제품 상세페이지
- 담아 놓은 제품들을 확인, 결제할 수 있는 장바구니
3. 프로젝트를 통해 배운 것
Firebase
프로젝트에서 가장 비중있게 다룬 API이다. Firebase는 빌드, 배포, 인증, 모니터링 등 서비스 관리를 위한 다양한 기능을 지원하는데, 본 프로젝트에서는 인증(Authentication)과 실시간 데이터베이스(Realtime Database)를 위해 사용하였다. firebase.js에 로그인, 로그아웃, 데이터베이스 쓰기 / 읽기 등의 로직을 용도에 맞는 함수로 만들어 관리하였다.
쓰면서 든 생각은, 프론트엔드 입장에서 말도 안되게 편하다는 것이다. 최근 혼자 프로젝트를 진행하며 DB나 Express같은 백엔드 관련 개발스택도 익혀야할 필요성을 느꼈었는데, Firebase의 숙련도를 높이는 쪽으로 학습해봐야 겠다는 생각이 들었다. 추가적으로 제품의 이미지 파일들은 Cloudinary를 통해 관리하였다.
useContext를 통한 글로벌 상태관리
useContext는 유튜브 구현에서도 사용하였고, todolist에서 다크모드 구현 간에도 사용했었다. 근데 사실 Provider니 커스텀 훅이니.. 이해하기 쉽지 않은 내용들이었다. 이번 프로젝트를 진행하면서야 비로소 감이 좀 잡힌 것 같다. 정리하면 createContext / useContext는 컴포넌트들끼리 전역적으로 상태(State)를 공유할 수 있게 해주는 훅이다. 여기에 커스텀훅을 입히는 건 단지 코드에 이해를 돕기 위한 부가적인 과정일 뿐이다. 이렇게 생성한 <Context.Provider> 태그를 자식 컴포넌트에 씌우면, 해당되는 모든 자식 컴포넌트에서 State를 공유할 수 있게 되는것이다.
본 프로젝트에서는 사용자 정보에 변동이 있을 때 호출되는 Firebase의 메서드인 onAuthStateChanged를 useContext를 사용하여 관리하였다. 변동사항이 생기면 useEffect를 통해 당시의 유저 정보를 State에 갱신하고, 자식 컴포넌트들에게 뿌려주는 형식으로 처리된다. 어드민 사용자 여부를 확인할 때도 사용되었다.
경로 보호
구현 간 어드민 계정만 접속할 수 있는 상품추가 페이지라던가, 장바구니 페이지같은 비정상적인 접근을 막아야 하는 케이스가 있었다. 이를 이중 라우팅을 통해 특정 조건 만족 시 {children}을 반환하고, 만족하지 않는 경우 Root 경로로 Navigate시키는 방식으로 구현하였는데, 굉장히 인상깊었다.
4. 궁금했던 점
왜 Firebase의 데이터베이스와 Cloudinary의 클라우드를 혼용할까?
본 프로젝트에서는 제품들의 이미지 데이터는 Cloudinary에, 나머지 데이터들은 Firebase를 통해 관리하고 있다. 처음엔 두 API의 역할이 겹친다고 생각했다. 그냥 싹다 데이터베이스에서 관리하면 되는거 아닌가? 근데 그게 아니다. 구조화된 데이터들은 DB를 통해 관리하는 것이 맞지만 용량이 큰 컴퓨팅 자원들은 클라우드에 저장해놓고 꺼내 쓰는 것이 일반적이며, 필요한 경우 해당 자원들을 URL로 변환해 DB에 저장한다고 한다. 특히 Cloudinary는 이미지 관련 클라우딩에 특화되어 있다. 사용자가 업로드하는 이미지들의 크기를 제한하거나, 자동으로 변환하여 저장해준다고 한다.
useContext와 useQuery의 포지션이 겹치는 것 아닌가?
useQuery를 통해 key를 부여해 원격 데이터를 캐싱해놓고 원하는 곳으로 가져와 사용하는 거면, useContext와 다를 게 없는거 아냐? 라고 생각을 했었다. 내가 생각한 부분은 확실히 교집합이 맞지만, 각 훅에 대한 차이점과 목적에 초점을 두어 살펴볼 필요가 있었다. useQuery는 원격 데이터를 효율적으로 가져오는 작업을 위해, useContext는 애플리케이션 내부에서 공유되는 상태 값 관리를 위해 사용된다.
정리하고 보니 두 항목 모두 비슷한 로직을 갖는 것들에서 오는 혼란이었다. 각 용도와 차이점을 정확하게 파악하고, 필요한 경우 적절히 사용할 줄 아는 것이 중요하다는 생각을 했다.
5. 마치며
중요한 것
본 프로젝트를 진행하며 느낀 점은, 인터페이스 컴포넌트들과 비즈니스 로직들을 분리해 놓는 것이 굉장히 중요하다는 것이었다. 또한 비슷한 로직들은 하나의 커스텀 훅으로 묶어서, 호출하여 사용하는 방식이 컴포넌트 간의 의존성을 낮추고 유지보수를 용이하게 하는 방법이라 생각된다. 여기서 프론트엔드와 퍼블리셔가 갈리는 게 아닐까?
앞으로 📚
- useQuery - Query State, Invalidate Queries, Key Caching..
- FIrebase
- TDD
- 컴포넌트 의존성과 재활용성