토이 프로젝트 [Vocab] 회고
프론트엔드 개발자로 전향하기 위해 최근 리액트를 배우고 있다. [리액트를 다루는 기술]이라는 책 한권을 완독했고, 게시판 만들기보다 더 재미난걸 만들어 보고 싶어 고민하다가 단어장이라는 웹을 만들어 보았다.
자바스크립트는 알지만 리액트뿐만 아니라 처음 접하는 라이브러리가 많았기 때문에 원하는 걸 구현하려면 어떻게 사용하면 되는지 문서를 찾느라 고생했다. 그래서 코딩하는 시간보다 구글링하는 시간이 더 많았다.
개발 기간은 총 7일 걸렸다(첫 커밋 ~ 마지막 커밋). 순수 개발시간은 아마 30시간정도 되지 않을까 싶다.
# 구상
게시판처럼 리스트 조회/수정/삭제가 있으면서 간단한 걸 만들어 보고 싶었다. 초기 UI 구상은 피그마를 사용하면 참 좋겠지만 할 줄 모르니... 아래와 같이 종이에 끄적거렸다. 처음엔 이랬다는 것을 기록에 남기고자 종이를 버리기 전에 찍어 두었다. 다시 보니 결과물이랑 완전히 다르진 않은 것 같다. 😆
# 고충
크게 기억나는 몇 가지만 적어보았다.
🔥 Firebase
워낙 간단한 프로젝트라서 SpringBoot로 REST-API를 금방 만들 수는 있지만 프론트에 집중을 한 프로젝트이기 때문에 이번에 파이어베이스를 처음 사용해보기로 했다. 덕분에 파이어베스 사용방법 찾느라 전체 개발시간의 50%는 쓴 것 같다 ㅋㅋㅋㅋㅋ
- 총평: 백엔드 서버/디비 개발 없이 개발을 쉽게 해주기 때문에 API는 고민할 필요가 없어 거부감 없고 간단하긴 했지만, 웹에서는 지원안하는 기능이 많았다. 그래서 많이 아쉬웠다.
1) Firestore 생성 문제
시작부터 대 난관이었다. Firestore Database를 생성하려는데 자꾸 알수 없는 오류가 생겼다면서 생성이 안되었다.
구글링해보니 계속 "만들기" 누르면 된다던데 저는 안되고... 새로고침하고 다시 시도해도 안되고...
진짜 무슨 신이 그냥 프론트 개발자 하지 말라고 계시를 주는줄 알았다.
ㅋㅋㅋㅋㅋㅋㅋㅋㅋ 몇분째 난리 치다가 크롬 캐시 삭제해볼까 했는데 떠 있는 탭들을 닫으면 다시 찾기 힘드니 그냥 사파리로 접속해보니 생성되어 있었다. 🥲
2) Firestore 하위 컬랙션 문제
내가 처음 파이어베이스를 사용해보는 것이기 때문에 하위 컬랙션을 다루는 방법을 몰라서 발생한 문제이다.
구상한 모델은 매우 간단한 구조였다.
그래서 category안에 chapter 컬랙션을 생성하고, chapter 컬랙션 안에 vocabulary 컬랙션을 추가해서 관리하면 되겠다 싶었고, 아래와 같이 초기 데이터를 넣어줬다.
그런데 이게 조회나 트랜잭션같은 작업이 필요했는데 구글링을 해도 정보가 많이 안나왔다. 트랜잭션으로 한번에 생성하거나 삭제하고 싶었는데 트랜잭션을 하위 컬랙션에 거는 방법도 모르겠고, 가장 중요한건 하위 컬랙션 조회는 웹에서는 지원을 안한다!
그래서 결국엔 각각의 컬랙션을 따로 생성하는 방식으로 변경해서 해결했다.
3) Firestore 조건 검색과 정렬
조회할 때 조건 검색하고 결과에 대한 정렬(orderBy)도 하고 싶었는데, 이와같이 orderBy를 두 번 써봤지만 이것도 안된다. (where절을 두번 써도 안된다...ㅎ)
const q = query(
collection(db, "chapter"),
where("categoryId", "==", categoryId),
orderBy("categoryId", "asc"),
orderBy("order", "asc")
);
그래서 반환되는 양이 많거나 무겁지는 않으니 데이터를 where절로 조건 검색 후 따로 sort()를 이용해서 정렬하는 방식으로 해결했다.
( 2/25 추가 글 > 색인을 사용하면 된다... ㅎ )
🫨 리액트 상태 관리
Vue2 를 사용해본 사람으로써, 자식에서 부모컴포넌트로 데이터를 전송할 때 진짜 참 쉬웠는데... 리액트는 부모에서 자식으로만 속성 값을 전달할 수 있다보니 처음엔 어떻게 해야하나 고민이 많았었다.
헤더에 있는 메뉴나 다크모드와 같은 props로 넘기기 불편하거나 광범위하게 사용하는 데이터들은 저장소를 사용했다. 이번 프로젝트에서는 Context API를 이용했다. Main 파일에 Context 등록이 여러 개 되어 있어 컨텍스트 안에 컨텍스트 안에... 반복되다보니 Redux와 비슷한데 사용이 간단한 Zustand로 바꾸려고 했다. 하지만 두가지 이유로 변경하지 않았다.
- FireStore만으로도 벅찬데 Zustand를 검색하면서 하려니 벅참. 😅
- 상태값 바꾸니 구독하고 있는 모든 컴포넌트가 다 재랜더링되는 문제가 발생. → 이건 나중에 Selector를 이용하면 해결 된다는 걸 알았다.
가장 친숙한 걸로 만들었다는 점이 아쉽긴하다. 그래서 다음 토이 프로젝트때는 Zustand를 사용해보아야겠다.
🦾 리액트 최적화
- 총평: 최적화관련해서는 그 라이브러리(또는 언어)에 대해 좀더 깊게 공부해야하기 때문에 어느 언어든지 간에 어려운 것 같다.
1) useEffect
라이프사이클은 이해했고, useEffect 를 통해 마운트된 후 작업을 할 수 있으며, 두 번째 인자를 이용해서 언제 실행될지 설정가능하다는 것까지 알았다. 하지만 useEffect 를 사용하는게 공짜가 아닐 뿐더러 잘못 구독하고 있는 경우 불필요한 랜더링을 발생하기 때문에 useEffect 사용하는데 고민이 많았다. "이 기능을 구현하는데 useEffect를 사용하지 않고 구현할 수 있는 방법이 있을까?"를 먼저 고민하고 사용하도록 했다.
2) useCallback, useMemo, React.memo
이것들 또한 남용 시 오히려 성능을 저하시키고 메모리를 잡아먹기 때문에 고민이 많았다. "과도한 남용"이 어디까지인지를 모르겠어서 고민이 많았다. 물론 매우 작은 프로젝트라 사용하든지 말든지 성능의 차이는 크게 차이나진 않지만 사용해보고 싶었고, 이 프로젝트에 useMemo 한 번 사용했다. 그리고 useCallback 사용하기보다 굳이 재랜더링이 필요 없는 함수들은 컴포넌트 함수 밖에 선언했다.
𝑻 타입스크립트
타입스크립트도 처음 사용해 본 것이었는데, 리액트에 타입스크립트 함께 많이 사용하기 때문에 간단하게 영상 몇 개랑 문서를 보고 프로젝트를 시작했다. 전달되는 값이나 리턴되는 값의 타입이 무엇인지는 알긴 하는데 리액트 함수들이나 firestore 메소드들에 대한 리턴 값을 잘 모르겠어서 힘들었다. 물론 any로 다 때려박으면 되지만 그러면 타입스크립트를 하는 이유가 없어지기에 타입을 찾기 위해 노력했다.
타입스크립트를 쓰니 잘못 선언하면 빌드 시 오류나서 귀찮긴 했지만 특히 객체 같은 경우 안에 속한 키들이 자동으로 보이기 때문에 좋긴 했다.
이 프로젝트에 타입스크립트가 완벽히 타입을 잘 맞춰했다고는 장담하지 못한다. 그렇기 때문에 다음 프로젝트도 계속해서 타입스크립트를 사용해보도록 할 것이다.
👔 스타일
나는 (다행히...) CSS(SCSS)에 대한 어느 정도 기본 지식은 있었고 styled-Components와 tailwindCSS 둘 중 고민했는데, 작년에 인기있었던 shadcn/ui를 사용해보고 싶어서 tailwindCSS를 사용했다. 그런데 생각보다 단점도 꽤 있었다.
- 클래스가 무진장 길어짐. → 그래서 ui 컴포넌트로 따로 분리하게 됨.
- 상세(세세)한 설정이 어렵다. → 원하는 크기로 수정이 불편하다. 애니메이션도 불편했다.
- 속성 찾느라 시간 다감. → 다행히 어떤 css를 사용하면 된다는 건 알았기 때문에 검색은 쉬웠다.
그래서, 중간에 styledComponent도 같이 사용할까 고민 많이 했지만... 몇 개만 css module로 해결하고 나머지는 tailwindCSS로 다 했다. (만세) 🙌
# 결론
리액트라는 라이브러리는 처음이지만 나는 풀스택으로 개발해왔기 때문에 프론트도 조금은 다룰 줄 알았다. 그래서 책 한권 대~충 훑고 프로젝트를 시작했는데 역시 책과 실전은 완전히 다르다는 것을 다시 한 번 느꼈다 🤪
아쉬운 점이 더 많지만 처음이기 때문이라고 자기 위안하기로 했다. 🍻 다음 토이 프로젝트엔 더 다양하게 적용보고 더 깊게 배울 수 있도록 해야겠다.
👍 잘한 점 2가지
- 새로운 라이브러리를 많이 사용했는데 원하는 대로 구현을 다 한 점. ( 물론 결과보다 과정이 중요하긴 하지만... )
- 반응형으로 작업한 점.
👎 아쉬운 점 4가지
- 테스트 코드를 작성하면서 개발하지 않은 점. ( 테스트 코드 작성하는 법은 배우긴 했는데 실제 적용하기가 너무 어려웠다. )
- react-router-dom과 tanstack(react-query)를 좀 더 활용해보고 싶었는데 못했다. ( loader, action, mutate 등 )
- context API 만 사용한 점...
- firebase 인증 기능을 사용하지 않은 점 ( 로그인 기능을 구현하지 않았기 때문에 사용하지 않았다. )
제가 작성한 코드에 코드 리뷰(평가)도 받고 싶은데 언제든지 제 깃헙에 남겨주세요! 환영입니다! 🫶