STAYFOLIO 사이트 클론 프로젝트 회고록

Jay Yu·2022년 7월 27일
0
post-thumbnail

본디 계획은 1차 프로젝트를 배포하고 난 후에 바로 회고를 작성하는 것이었지만 추가적으로 구현하고자 하는 기능과 코드 리팩토링에 대한 욕심이 커 미처 시간을 내지 못하고 있다가 2차 프로젝트까지 마치고 기업협업을 앞두고 있는 이번주에 회고를 작성하게 되었습니다.

📌 프로젝트 소개

STAYFOLIO 는 숙박 예약사이트로 국내 뿐만 아니라 해외숙박시설또한 예약할 수 있는 사이트입니다. 단순히 숙박 시설을 보여주고 예약하는게 아닌 각 숙박시설의 스토리들을 큐레이팅하여 정보를 제공하는 사이트로서의 역할도 수행하고 있습니다.

  • 개발 기간 : 2022-06-20 ~ 2022-07-08
  • 개발 인원 : 5명 (프론트 1명, 풀스택 4명)
  • 기술 스택 : React, Javascript(ES6), HTML, SCSS, NodeJS, ExpressJS, MySQL, Prisma

📌 역할 배분

  • 기존
  1. Main페이지 - 상민
  2. 회원가입/로그인/footer - 관희
  3. Find페이지/Nav - 광현
  4. Detail페이지 - 상현
  5. Reservation 페이지 - 재훈
  • 개선
  1. Main페이지 - [F]상민, [B]광현
  2. 회원가입/로그인/footer - [F],[B]관희
  3. Find페이지/Nav - [F]광현, [B]상민
  4. Detail페이지 - [F]상현, [B]재훈
  5. Reservation 페이지 - [F]재훈, [B]상현

프로젝트 첫번째 날인 6월20일에 처음으로 팀원들끼리 모임을 가지고 나서 component나 기능별로 역할을 배분하기 보다는 page별로 역할을 나누기로 팀원끼리 합의를 하고, 또한 1차 프로젝트의 특성상 팀원 모두가 front단과 back단을 경험해보는 것이 목적이었기 때문에 각자가 각자의 페이지의 front단과 back단을 맡기로 했습니다.그러나 프로젝트 1주차가 마무리되는 시점에 그렇다면 과연 이것이 팀프로젝트의 목적에 부합하는가? 라는 의문과 멘토님의 조언을 바탕으로 2주차에 역할을 다시 분배하여 진행하였습니다. 그리고 그것이 올바른 선택이었다고 생각합니다.

DB 모델링

DB 모델링은 모든 팀원이 참가하여 이루어졌습니다.
DB 모델링에 참여하는 것, DB가 어떻게 이루어져있는지 알고 프로젝트에 참여하는 것이 의미있는 시간이었다고 느꼈습니다. 백엔드 개발자와 소통할때 터무니없는 요청을 하지 않을 수 있고, 또한 소통하는 데 있어서 trouble을 최소화시킬 수 있었기 때문입니다.

🔨 개발과정

제가 맡았던 역할은 header컴포넌트와 각각의 숙소를 나열하고 필터링해서 user에게 보여주는 find 페이지였습니다. 저는 프론트엔드 개발쪽으로 마음을 굳힌 상황이었기 때문에 최대한 UI/UX 적으로 완벽하게, 만들어보자는 목표를 가지고 이번 프로젝트에 임했었습니다.

Header 컴포넌트

Header 컴포넌트는 서비스 내에서 유일무이한 컴포넌트이고, 모든 페이지에서 사용되는 컴포넌트이기 때문에 find페이지를 개발하기 전 우선적으로 작업에 착수했습니다. 클론프로젝트의 모체인 stayfolio사이트의 header 컴포넌트는 click 이벤트가 발생시 modal이 등장하고 modal 내에서 또 클릭이벤트가 발생하면 해당 데이터를 가지고 find페이지로 이동하는 기능을 가지고 있었습니다.

그렇기 때문에 react-router-dom에서 제공하는 함수인 useNavigate와 query 파라미터를 활용해서 submit 버튼을 누를 시 선택한 정보를 가지고 url을 바꿔서 해당 url이 find페이지에서도 유효하고, 그 query 파라미터를 바탕으로 data를 서버에서 fetching 해올 수 있도록 하였습니다.

What I've focused and learned

Header 컴포넌트를 개발하면서 앞서 말했듯이 최대한 UI/UX 적인 면에 집중하였습니다. modal이 화면에 렌더링 될때에 부드러운 등장을 위해서 animation효과를 추가하였고, 모달의 바깥부분과 X버튼을 눌렀을 때 모달이 사라지도록 해야했으며, modal이 등장했을 때는 사용자가 modal에만 집중할 수 있도록 modal 바깥의 요소들에 대한 관심을 배제시키고, 실제로 작동하지 않게끔 만들어야했습니다. 그것을 위해서 z-index와 position 속성을 적절히 활용하였으며, useEffect 훅을 사용하여 modal이 mount된 이후 유저가 scroll 이벤트를 발생시키지 못하게 제약하였습니다.
마지막으로 모달을 사라지게 하는 과정에서 이벤트 전파에 대해서 알게되었고 그에 대해 공부할 수 있는 시간이었습니다.

이벤트 전파 - mdn 문서

Find 페이지

처음에 역할배분을 할 때, 팀원분들께 find페이지를 맡고 싶다고 먼저 적극적으로 의견표출을 했는데, 그 이유는 find 페이지는 제가 생각하기에 프론트엔드 개발자로서 다뤄야할 것들의 기본적인 사항들을 모아놓은 패키지같은 느낌이어서 였습니다. image slider 기능, 메뉴 dropdown, 여러가지 type의 input들, 필터링 기능, 정렬 기능, pagination 까지 ...

기본적인 logic과 코드 구현은 어렵지 않았지만 find 페이지 내부를 컴포넌트 별로 구분하는 데에서 고민이 많이 들었습니다. 컴포넌트를 분리시키고 해당 컴포넌트를 불러온다는 것은 필연적으로 props 전달을 해야하는데, 이 구조를 잘 설계하지 않으면 추후에 꽤나 지저분한 코드가 될 수 있겠다고 생각이 들었기 때문입니다.

/*find.js*/
<Searchbar />
<Stay />

그리하여 너무 세세한 단위로는 나누지 않고 여러가지 필터링 기능이 들어있는 Searchbar와 필터링 조건에 해당하는 숙박아이템들을 보여주는 Stay로 나누어 find페이지를 구성하였습니다.

What I've focused and learned

find페이지에서 back단으로 보내는 요청은 GET 요청이 유일했습니다. 그리고 stayfolio 사이트의 필터링 동작이 필터링 조건을 유저가 선택하면 해당하는 조건을 query parameter로 server에 보내는 구조라는 것을 파악했습니다.
사용자의 접속(요청)에 따라서 어플리케이션이 응답한 정보를 웹브라우저에 URL을 확인하여 정보를 가져오는 것을 GET 방식으로 일컫고 있는데 이를 위해서 데이터 페칭을 담당하는 useEffect 내부의 함수를 다음과 같이 작성했습니다.

  useEffect(() => {
    const fetchData = async () => {
      const result = await (
        await fetch(`http://${BASE_URL}:8000/findstay${location.search}`, {
          method: 'GET',
        })
      ).json();
      setData(result.data);
    };
    fetchData();
  }, [location.search]);

4번째 줄에 작성된 fetch함수의 첫번째 매개변수로 요청을 보내는 api의 주소를 기입했는데, 템플릿 리터럴 기법을 사용해서 작성하였습니다. location.search는 useLocation().search로도 치환이 가능한데 반환값은 다음과 같습니다.
?a=123&b=456&c=789
이해를 위해 예시를 위와같이 적었는데 url에 존재하는 query는 마치 객체와 같이 key와 value가 1대1로 맵핑되는 구조로 이루어져있습니다. 그리고 중간에 존재하는 '&'는 separator로서 동작합니다. 즉 다음과 같이 이해하는 것도 무방합니다.

query = {
		a : 123,
        b : 456,
        c : 789,
 }

useEffect 훅의 의존성 배열에는 location.search를 배정하여, location.search 값이 바뀔 때마다 새로운 요청을 server로 보내서 상응하는 데이터들을 페칭할 수 있도록 작성했습니다.

그래서 어떻게 동적으로 url을 변경시킬 것인가? (without Link태그, useNavigate 훅)

처음에는 useNavigate 훅을 사용해서 동적으로 url을 변경하고자 했습니다. 그러나 필터링 조건을 하나만 선택하면 잘 동작했지만 여러 필터링 조건을 선택하면 생각한 것처럼 동작하지 않는것을 확인했습니다.
즉, 이전에 설정해놓은 query parameter를 무시하고, 새롭게 덮어쓰는 것을 확인했습니다.
이는 제가 원하던 방식이 아니었습니다. 그래서 멘토님의 조언을 받아 useSearchParams 훅과 URLSearchParams에 대해서 구글링을 해서 해결했습니다.
useSearchParams에 대한 공식문서의 설명은 다음과 같습니다.

The useSearchParams hook is used to read and modify the query string in the URL for the current location. Like React's own useState hook, useSearchParams returns an array of two values: the current location's search params and a function that may be used to update them.

직역하면 useSearchParams 훅은 현재위치의 URL의 query string을 읽고 고칠 수 있습니다. useSearchParams의 첫번째 반환요소는 현재위치의 search params이고, 두번째 반환요소는 search params를 업데이트 할 수있는 함수입니다. 즉, useState()훅과 비슷하게 동작하는 것을 확인할 수 있습니다.

하지만 useSearchParams훅 만으로는 제가 원하는 기능을 구현할 수 없었습니다. 이유는 다음과 같습니다.
url을 검사하여 유저가 이미 url의 search params에 있는 옵션을(key), 값만(value) 변경하는 경우에는 덮어씌워야 하고, 그렇지 않은 경우에는 (다른 옵션을 선택하는 경우, 즉 key가 다른 경우) 기존에 설정되어 있던 search params에 이어붙여야 했습니다. 그래서 URLSearchParams를 사용하기로 결정했습니다.

URLSearchParams()는 Web API에서 기본을 제공해주는 생성자함수입니다. 덧붙여서 기본적으로 제공해주는 메소드중에서 저는 has()와 set() 메소드를 활용해서 url의 query 부분에 이미 key가 있는지(has 메소드 사용) 검사하여 분기점을 만들고, set()메소드를 적절히 활용하여 query를 변경하였습니다. 이 과정에서 코드의 재사용성을 고려하여 따로 util함수를 만들었습니다.

const url = new URLSearchParams(queries);
const [queries, setQueries] = useSearchParams();

function urlChange(target1, value1, target2, value2) {
    if (!target2) {
      if (url.has(target1)) {
        url.set(target1, value1);
        setQueries(url.toString());
      } else {
        setQueries(url.toString() + `&${target1}=${value1}`);
      }
    } else {
      if (url.has(target1)) {
        url.set(target1, value1);
        url.set(target2, value2);
        setQueries(url.toString());
      } else {
        setQueries(
          url.toString() + `&${target1}=${value1}&${target2}=${value2}`
        );
      }
    }
  }

그 결과 어떤 key값과 value가 들어와도 동적으로 url의 query 파라미터를 성공적으로 바꿀 수 있었습니다.

마무리하며...

부트캠프에 들어온 가장 큰 목적이 팀프로젝트를 경험해보기 위한 것이었기 때문에 1차프로젝트에 큰 기대를 가지고 시작했습니다. 그러나 팀원들간의 의사소통이 쉽지 않았던 부분, 다른 팀원의 기능이 마무리되어야 나의 기능을 진행할 수 있었던 부분들이 약간의 애로사항이었습니다. 그럼에도 불구하고 find페이지의 백엔드를 맡아주신 상민님과의 의사소통(data를 전달하는 방식과 내가 받았으면 하는 data의 형식 지정)은 여태까지 혼자 코딩해왔던 저에게는 큰 기쁨과 성취감이었습니다.
또한 적어도 내가 맡은 파트는 완벽하게 끝냈다는 뿌듯함은 더할나위 없이 좋았습니다.
이후에 작성할 2차 프로젝트 회고에서는 1차 프로젝트에서 미진한 부분을 어떻게 보완하고 자신감을 얻었는지에 대해서 다루어 보겠습니다.

profile
니체의 마인드셋으로

0개의 댓글