[우아한테크코스 FE 5기] 레벨2 다시, 점심 뭐 먹지 미션 회고

Chex·2023년 4월 24일
9

우아한테크코스

목록 보기
10/19
post-thumbnail

다시, 점심 뭐 먹지 페이지 실행화면

이번 미션에서 리액트와의 첫 만남을 가졌다. 첫 만남인 만큼 어색했고 어려웠으나 리액트의 편리함 또한 느낄 수 있었던 미션이었다.

이제 컴포넌트를 만들 수는 있다. 하지만 ‘컴포넌트를 잘! 만드는 것’을 고민해야할 때라고 느꼈다. 잘 만들어진 컴포넌트의 특징은 역시 높은 ‘재사용성’과 ‘예측 가능성’이라고 생각한다. 다른 개발자가 봐도 사용하기 쉽도록 말이다.

재사용성을 높이려면 적당히 잘! 쪼개진 컴포넌트를 만들어야하고 리액트에서 잘 쪼개진 컴포넌트를 만들기 위해서는.. 이번 2단계 미션에서의 요구사항이었던 'Custom Hook 만들기'에서, ‘어디서부터 어디까지를 담을지, 어떻게 하면 재사용성을 높일 수 있을지 더 많이 고민해봐야할 것 같다’고 생각했다. 아직도 커스텀 훅은 어떻게 구성해야할지 감이 잘 안오지만..ㅎㅎ;

📝 리액트 사용 후기

  • JSX문법을 사용하여 리액트 엘리먼트를 생성할 수 있어서 템플릿 리터럴을 사용하는 것 보다 편했다.
  • 개발자가 DOM API를 다룰 필요 없이 상태(State)를 기반으로 DOM을 업데이트 시켜준다는 점이 좋았다.
  • 엘리먼트 생성 후 이벤트 리스너를 달아주기 위해 addEventListener를 호출 할 필요 없이 엘리먼트가 처음 렌더링 될 때 리스너를 달아주면 돼서 편했다.

📝 도메인과 관련된 데이터를 관리하는 도메인 객체 만들기

컴포넌트와 도메인 데이터를 다루는 로직 간 관심사 분리를 위해 도메인 데이터와 관련한 로직을 담당하는 도메인 객체를 만들주었다.

점심 뭐 먹지 미션 프로그램 구조도

LunchDataService: 도메인 데이터를 localStorage(앱 외부 저장소)로부터 받아오고 가공해서 전해주는 등 도메인 데이터를 관리하는 도메인 객체

interface LunchDataServiceType {
  restaurants: Restaurant[];
  setInitialRestaurants(): void;
  filterBy(category: Category): Restaurant[];
  sortBy(criterion: Criterion, restaurants: Restaurant[]): Restaurant[];
  filterAndSort(category: Category, criterion: Criterion): Restaurant[];
  getRestaurant(id: string): Restaurant;
  getRestaurants(category: Category, criterion: Criterion): Restaurant[];
  getProcessedRestaurants(category: Category, criterion: Criterion): Restaurant[];
}

const LunchDataService: LunchDataServiceType = {
  restaurants: [],
  ...
};

📝 Class 컴포넌트를 Function 컴포넌트로 마이그레이션 하기

  • 이벤트 핸들러 주입 시 this 바인딩이 필요 없다.
  • 같은 기능을 더 짧은 코드로 구현할 수 있다.
  • Component를 상속받을 필요가 없어서 컴포넌트 선언이 더 편하다.
  • state, lifeCycle 관련 기능은 Hook을 통해 사용할 수 있다.
  • 클래스컴포넌트의 경우 setState로 state를 변경했고 함수컴포넌트의 경우 useState로 state를 바꿔주는 함수를 반환받아 사용한다.

📝 반복되는 컴포넌트는 분리하여 재사용하기

Problem

클래스 컴포넌트에서 반복되는 컴포넌트를 재사용하기 위해 고민했다. 특히 상속과 조합 중 어떤 구조를 이용하여 컴포넌트를 재사용할 수 있게 만들지 고민을 많이 했다.

처음엔 상속 구조를 선택하였으나 조합방식으로 구조를 변경했는데 이유는 다음과 같다.

  • 리액트 공식문서

    React는 강력한 합성 모델을 가지고 있으며, 상속 대신 조합을 사용하여 컴포넌트 간에 코드를 재사용하는 것이 좋습니다.

  • 상속보다는 조합을 사용하자.

    상속의 단점: 캡슐화를 깨뜨린다. → 하위 클래스가 상위 클래스에 강하게 결합, 의존하게 된다. → 변화에 유연한 대처가 어려워짐

    캡슐화: 타인이 외부에서 조작하는 것에 대비해 외부에서 특정 속성이나 메서드를 사용할 수 없도록 숨겨놓는 것

// DetailModal.tsx (조합 방식으로 Modal컴포넌트를 재사용해서 만든 컴포넌트)

<Modal>
  <div className="restaurant__info">
    /* content of DetailModal */
  </div>
</Modal>

📝 ‘사용자 정의 타입’ 가드 만들기

사용자 정의 타입 가드 만들기.png

음식점 카테고리를 정의하는 Category타입과 음식점 목록의 정렬기준을 정의하는 Criterion타입을 만들었다.

Problem

event.target.value는 string 타입이어서 Category, Criterion 타입에 할당할 수 없었다.

Try

// guard.ts
export function isCategoryType(input: string): input is Category {
  const categories = Object.values(CATEGORY);

  if (categories.includes(input)) return true;

  return false;
}

export function isCriterionType(input: string): input is Criterion {
  const criterions = Object.values(CRITERION);

  if (criterions.includes(input)) return true;

  return false;
}

타입 가드를 정의하기 위해 반환 타입이 타입 서술어(input is Category)인 함수를 정의했다. isCategoryType(input)이 호출될 때 기존 타입과 호환된다면 TypeScript는 inputCategory 타입으로 제한한다.

// CategoryFilter.tsx
interface CategoryProps {
  setCategory: (newCategory: Category) => void;
}

function CategoryFilter({ setCategory }: CategoryProps) {
  const handleCategoryChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
    if (isCategoryType(event.target.value)) setCategory(event.target.value);
  };

  return <SelectBox filter={CATEGORY} handleOptionChange={handleCategoryChange} />;
}

Be the best version of you!

profile
Fake It till you make It!

14개의 댓글

comment-user-thumbnail
2023년 4월 24일

멋있네요 첵스 역시 시리얼은 첵스초코~

1개의 답글
comment-user-thumbnail
2023년 4월 24일

글 잘 읽었습니다😄

사용자 정의 타입이 정말 멋져요! 저도 미션 중에 필요하다면 사용해봐야겠습다:)

: input is Category 이러한 문법을 지칭하는 용어가 있나요?

1개의 답글
comment-user-thumbnail
2023년 4월 25일

하핳 첵스 글 잘 보고 갑니당~~!

1개의 답글
comment-user-thumbnail
2023년 4월 25일

첵스! 정리를 잘해놓으셨네욥 덕분에 제 회고글도 덩달아 풍성해졌어요!👍👍

2개의 답글
comment-user-thumbnail
2023년 4월 28일

안녕하세요 첵스~!

저도 셀렉트 할 때 event.currentTarget.value가 string 값이라며 오류를 뿜어내더라구요! 저도 겪었던 오류를 첵스의 글에서 만나서 반가웠습니다 😀

저는 타입 가드할 때 동일한 로직이 반복되는 것 같아 hook으로 분리해보았어요.

useSafeUnionTypeState.ts
https://github.com/Gilpop8663/react-lunch/blob/step2/src/hooks/useSafeUnionTypeState.ts

사용 예시 )

  const [selectedCategory, setSelectedCategory] = useSafeUnionTypeState<FoodCategory>('전체', FOOD_CATEGORY);

  const [selectedSortingMethod, setSelectedSortingMethod] = useSafeUnionTypeState<SortMethod>('이름순', SORT_METHOD);


  const onChangeSelect = (value: string, kind: 'filter' | 'sort') => {
    if (kind === 'filter') {
      setSelectedCategory(value);
    }

    if (kind === 'sort') {
      setSelectedSortingMethod(value);
    }
  };

글 잘 읽었습니당 👍👍👍

--우스--

1개의 답글
comment-user-thumbnail
2023년 4월 28일

항상 응원해요 첵스~~~ 파이팅파이팅!!

1개의 답글

관련 채용 정보