티스토리 뷰

회고

[팀프로젝트] 저기어때 회고

2021bong 2022. 10. 3. 18:14

✨프로젝트 소개

여기어때 모티프 프로젝트

 

사이트 선정 이유

이전 프로젝트로 다들 이커머스를 진행해 새로운 유형의 사이트를 하고 싶어 선정하게 되었다.

FRONT 프로젝트 목표

  1. Styled-components, Axios, Recoil, React-datepicker 등 새로운 기술을 사용해 본다.
  2. 이전에 해본 적 없는 기능을 맡아서 구현해 본다.

진행 기간

2022.09.19 - 09.30 (2주)

GitHub repo

프론트


사용된 기술

  • JavaScript
  • React
  • Styled-components
  • Recoil
  • Git, GitHub

props drilling이 심각하게 발생하거나, 파일이 많아지면 state가 어디서 관리되고 있는지 알기 어려운 문제를 해결하고자 상태 관리 라이브러리를 사용하기로 했다. Redux는 2주라는 짧은 프로젝트 기간 동안 배워서 사용하기 어렵다고 판단해 Recoil을 사용했다.

 


협업 방식

  • PLANNING MEETING

프로젝트 1,2,3일 차에 전체적인 계획과 흐름에 대해 상의했다.

 

여기어때 사이트의 기능 중 어디까지 구현할지와 프로젝트 목표에 대해 정했다. DB모델링에 프론트 팀원들도 함께 참여했다.

큰 계획을 정한 뒤에 포지션끼리 따로 회의를 진행했다. 미리 준비했던 컨벤션과 라이브러리 사용을 제안했고 잘 받아들여졌다. 스타일드 컴포넌트 라이브러리를 처음 써보는 사람을 위해 노션에 사용법을 공유하기로 했다. 그리고 각자 하고 싶은 기능 위주로 역할을 나눴다.

 

 

  • DAIRY STANDUP MEETING

매일 오전 10시에 10분 정도 데일리 미팅을 진행했다. 각자 어제 한 일, 오늘 할 일, 블로커를 공유했다. 추가적으로 논의가 필요한 사항이 있다면 이 시간에 회의도 함께 진행했다. 


역할

달력 라이브러리를 사용해보고 싶어서 숙소 리스트를 보여주는 카테고리 페이지와 해당 페이지의 필터를 담당했다.

 

  • 카테고리 페이지 (숙소 리스트 페이지) + 탑버튼

useParams를 사용해 parameter를 받아와서 SideFilter 컴포넌트에 props param으로 넘겨주면 값마다 다른 옵션들이 구성되게 했다. param값에 따라서 handleShowBedtype나 handleShowRange 함수로 어떤 필터 옵션을 보여줄지 정하고 options.js에 선언해둔 옵션리스트에서 몇 번째 목록을 보여줄 건지 정하는 handleSelectFilter함수를 이용해 SideFilter가 동적으로 구성되게 했다.

 

  • 달력

달력 라이브러리를 이용해 보고 싶어서 카테고리 페이지를 맡았다. 그런데 생각보다 필요한 조건에 맞는 라이브러리를 찾기가 힘들었다. 기간이 잡혀야 하고 달력이 하나만 뜨는 라이브러리를 찾아서 3번 정도 라이브러리를 변경했고 최종적으로 React-datepicker를 사용하기로 했다. 기본 디자인이 못생겨서 이리저리 CSS를 바꿔가면서 원하는 모양으로 커스텀하고, 상세 페이지에서 다른 정렬 방식으로 달력을 쓸 수 있도록 props align을 받아 정렬을 바꿀 수 있도록 했다. cmd+클릭으로 컴포넌트가 가진 속성 ts파일에서 년월 표시방식 속성을 찾아 변경했는데 앞으로 외부 라이브러리를 쓸 때는 이 방법을 자주 사용해야겠다.

 

 

 

 

  • 지도

지난 프로젝트 때 카카오맵 API를 사용해서 금방 끝낼 줄 알았는데 핀의 개수와 인포윈도우의 개수가 달라서 조금 더 찾아봐야 했다. 숙소 목록의 list를 받아서 요소의 위도, 경도마다 핀을 찍고 해당 핀에 인포윈도우도 달아줬다. 그래서 지도에 여러 개의 핀이 보이고 핀에 마우스 호버를 하면 해당 숙소의 이름과 가격이 보이도록 했다.

 

 

  • 필터 기능

필터의 체크박스에 체크를 하면 쿼리스트링을 관리하는 배열인 전역 state에 체크박스의 내용에 해당하는 쿼리스트링이 들어간다. 그리고 체크를 해제하면 배열에서 그 쿼리스트링을 삭제했다. 이용시설 옵션이나 침대 타입과 다르게 인원수는 중복이 되면 안되기때문에 적용버튼을 눌렀을 때 배열에 들어가고 그 쿼리스트링 배열을 합한 url로 서버에 요청을 보냈다. 그럼 서버에서 해당 조건에 맞는 데이터만 넘어온다.

 

 

 

 


😀잘한 점

  • 컨벤션 및 문서화

지난 1차 프로젝트 때는 컨벤션을 거의 정하지 않고 진행한 게 아쉬웠다. 이번에는 컨벤션을 꼼꼼하게 정하고 싶어 필요하다고 생각한 컨벤션을 모두 정리해갔다. 다른 팀원의 제안까지 합해서 컨벤션을 세세하게 정하고 시작했다. 그 덕에 서로 코드 리뷰 하기도 편했고 커밋과 코드를 통일성, 가독성 좋게 관리할 수 있었다고 생각한다. 사용하기로 한 라이브러리 이용 예시와 내가 만든 컴포넌트 재사용 안내도 대화로 끝내지 않고 기록해서 문서로 남겨둔 점이 잘했다고 생각한다.

컴포넌트 재사용 안내를 작성하면서 타입스크립트를 사용했다면 따로 글을 작성할 필요가 없었을 텐데 하는 생각이 들면서 타입스크립트의 필요성을 느꼈다..🥺

 

  • 유저 사용성

이전까지는 UI와 기능 구현에 급급해서 유저 사용성에 대해서 크게 생각하지 못했다. 하지만 이번에는 이전보다는 조금의 여유가 생기기도 했고 유저 사용성이 좋지 않은 서비스를 이용할 때마다 스스로 불평하던 것이 생각나 유저 사용성을 챙기기로 했다. 비개발자인 친구의 의견을 수렴해 확실히 클릭한 것처럼 보이도록 기본 스타일과 반대되는 배경 색상을 active 상태에 추가했다. 유저의 입장에서 눌렀을 때 아무 변화가 일어나지 않으면 클릭했는지 확신할 수 없으니 앞으로는 active 상태의 스타일링을 꼭 해야겠다고 생각했다. 또 모달창이 떴을 때 X아이콘 말고 바깥 배경을 눌렀을 때 모달이 꺼지는 기능이 없으면 짜증 나는 사람으로서 이 기능도 추가했다. 

 

  • 재사용할 수 있는 컴포넌트에 대한 고민

내가 맡은 페이지의 컴포넌트들을 검색 페이지에서도 써야 했다. 검색 페이지는 다른 팀원이 맡았기 때문에 컴포넌트를 쉽게 이용할 수 있도록 만들어야 했다. 탑 필터는 UI 변화가 없어서 문제가 없었는데 사이드 필터는 넘겨주는 props에 따라 컴포넌트 UI가 달라져서 재사용을 하기 위해서는 코드를 추가해야 했다. 내가 맡은 카테고리 페이지의 필터들은 useParams의 category 값을 넘겨받아 옵션이 다르게 나오도록 했는데 검색 페이지에서 props param으로 문자열 'search'를 넘겨주면 검색페이지에 맞는 옵션이 나오도록 코드를 추가했다. 또 되도록 컴포넌트가 받는 props를 개수가 적고 간단하게 하기 위해 고민했다.

 

  • 코드 가독성에 대한 고민

위처럼 내가 작성한 코드를 다른 사람이 읽어야 하기도 하고 필터 옵션 내용에 들어가는 문자열처럼 단순한 선언이나, 쿼리스트링으로 이용할 값을 뱉는 단순히 파라미터에 따라 문자를 리턴하기만 하는 함수처럼 컴포넌트의 맥락에서 중요하지 않은 코드들이 길어져 가독성이 떨어지는 것을 막기 위해 별도의 파일로 분리했다. 컴포넌트 내부의 state나 함수들도 조금이라도 가독성을 높이기 위해 사용된 순서에 따라 재 정렬했다.

 


✅자랑하고 싶은 코드

useParams를 사용해 url parameter를 받아와서 값에 따라 어떤 옵션 필터를 보여줄지 결정하는 함수들과 옵션 목록의 몇 번째 내용을 보여줄지 결정하는 함수를 사용해서 페이지마다 필터가 다르게 보이도록 했다.

...

<aside>

...

      <section className='no-title mt32 mb18'>
          <ul>
            {options[handleSelectFilter(param)].availablePromotion &&
              options[handleSelectFilter(param)].availablePromotion.map((el) => {
                return (
                  <CheckItem
                    text={el}
                    key={el}
                    id={el}
                    getOptions={getOptions}
                    checked={checked}
                    setChecked={setChecked}
                  />
                );
              })}
          </ul>
        </section>

        {options[handleSelectFilter(param)].typeList && (
          <OptionList
            list={options[handleSelectFilter(param)].typeList}
            title={options[handleSelectFilter(param)].type}
            getOptions={getOptions}
            checked={checked}
            setChecked={setChecked}
          />
        )}

        {options[handleSelectFilter(param)].theme &&
          options[handleSelectFilter(param)].theme.map((type, i) => (
            <OptionList key={i} list={type} getOptions={getOptions} checked={checked} setChecked={setChecked} />
          ))}

        {handleShowCount(param) && (
          <section className='count-container mt32 mb18'>
            <h5 className='title'>인원</h5>
            <div className='count-box'>
              <Down count={count} className='down'>
                <FontAwesomeIcon icon={faMinus} onClick={handleCount} />
              </Down>
              <span className='count'>{count}</span>
              <Up count={count} className='up'>
                <FontAwesomeIcon icon={faPlus} onClick={handleCount} />
              </Up>
            </div>
          </section>
        )}

        {handleShowBedtype(param) && (
          <section className='bedtype-container mt32 mb18'>
            <h5 className='title mt32 mb18'>베드 타입</h5>
            <ul>
              {bedtype.map((type, i) => {
                return type.selected ? (
                  <li key={type.id} index={i} onClick={handleSelectBedtype}>
                    <div className={`icon ${type.class} selected`}></div>
                    <p className='selected'>{type.title}</p>
                  </li>
                ) : (
                  <li key={type.id} index={i} onClick={handleSelectBedtype}>
                    <div className={`icon ${type.class}`}></div>
                    <p>{type.title}</p>
                  </li>
                );
              })}
            </ul>
          </section>
        )}

        {handleShowRange(param) && (
          <section className='price-container mt32 mb18'>
            <h5 className='title mt32 mb18'>
              가격{value[0] !== 1 && <span>{value[0]}만원이상</span>}
              {value[1] !== 30 && <span>{value[1]}만원이하</span>}
            </h5>
            <div className='slider-box'>
              <CustomSlider value={value} handleChange={handleSilderChange} />
            </div>
            <div className='price-range'>
              <p>1만원</p>
              <p>30만원</p>
            </div>
          </section>
        )}
        {options[handleSelectFilter(param)].options &&
          options[handleSelectFilter(param)].options.map((type, i) => (
            <OptionList key={i} list={type} getOptions={getOptions} checked={checked} setChecked={setChecked} />
          ))}
      </div>
    </aside>
    
 ...

 

 

처음에는 체크박스의 check state를 체크박스 컴포넌트에서 관리했다. 하지만 부모 컴포넌트의 초기화 버튼으로 전체 체크 해제를 해야 하는 상황이 생겨 어떻게 구현할지 고민하다가 필터 체크박스 목록들이 동적 구현되는 것과 같은 방식으로 check state에 대한 배열을 동적으로 생성해 부모 컴포넌트에서 관리하고 props로 내려줬다. 코드를 수정하기 싫어서 useImperativeHandle까지 사용해보고 다른 방법을 찾다가 안돼서 결국 정석대로 state를 props로 넘기는 방법으로 해결했다. 코드를 수정하기 싫어서 꼼수를 부리는 것보다 코드를 수정하는 것이 더 빠르다는 걸 깨닫게 되었다...🥲

  const theme = options[handleSelectFilter(param)].theme
    ? options[handleSelectFilter(param)].theme.map((el) => el.optionList).flat()
    : undefined;
  const filterOptions = options[handleSelectFilter(param)].options
    ? options[handleSelectFilter(param)].options.map((el) => el.optionList).flat()
    : undefined;
    
  const checkArr = [
    options[handleSelectFilter(param)].availablePromotion,
    options[handleSelectFilter(param)].typeList,
    theme,
    filterOptions,
  ]
    .flat()
    .filter((el) => el !== undefined)
    .map((el, i) => {
      return { name: el, checked: false };
    });

 

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함