[React/typescript] 재사용 가능한 동적 타입 리스트 컴포넌트 만들기 (generic type/ render props pattern)
Code/TypeScript

[React/typescript] 재사용 가능한 동적 타입 리스트 컴포넌트 만들기 (generic type/ render props pattern)

반응형

Photo by Nick Fewings on Unsplash

담당하고 있는 서비스가 만들어진지 곧 1년이 다 되어갑니다.

MAU 5천만의 업계 최고 플랫폼을 만들어 내고야 말겠다는 꿈을 꾸며 항상 코드를 작성할 때 먼 미래를 생각해 작성했던 것 같은데 각오가 무색하게 3개월만 지나도 '왜 이렇게 했지'라는 생각이 듭니다.

그땐 최선이라 생각했던 것들이 지금 와서는 아쉬운 게 투성이인지라 개발 범위가 겹치는 것이 있다면 본 일정에 무리 없는 선에서, 또는 개선해야만 하는 것들은 공유하며 마감 기한을 늘려보는 식으로 거슬리는 것들을 조금이라도 개선해나가며 작업하는 습관을 가지게 된 것 같습니다. 

반응형

요즘엔 리액트 디자인 패턴을 공부하며 체화 시키기 위해 노력 중인데 신기한 게 공부하다 보면 '어 이거 DynamicInput 컴포넌트에 쓰면 딱이겠다', '이거 온보딩 컴포넌트에 쓰면 너무 좋겠다' 이런 부분이 딱 떠오르는 게 재밌는 것 같습니다. 

디자인패턴 하나 배웠다고 신나서 막 이것저것 바꾸면 디자인 패턴의 존재 이유인 좋은 코드를 작성하는 목표와 멀어진다 생각하기에 진짜 적절한 부분에만 적용하도록 주의하고 있습니다.

프로젝트 사이즈가 많이 커진 상태라 이런 저런 고민이 많은 상태인데, 오늘은 render props 컴포넌트를 구현하며 배열 렌더링을 위한 컴포넌트 재사용성을 좀 더 확장 시킨 과정에 대해 작성해보려고 합니다.

 

문제의 코드

1

        <div className='overflow-y-scroll w-full pt-16pxr'>
          {resultPois.map((poi, index) => (
            <SearchResultItem
              key={poi.id}
              onClick={() => {
                setDeparturePoint(poi);
              }}
              text={poi.name}
              subText={poi.address}
            />
          ))}
        </div>

2

 <>
    <ResultTitle text='찜한 장소' />
     {savedPois.map((poi, index) => (
       <SearchResultItem
          key={`savedPoi_${index}`}
          text={poi.name}
          subText={poi.address}
          leftIconType='heart'
          onClick={() => onClickPoi(poi)}
       />
	))}
 </>

두 코드 모두 배열을 맵핑하여 SearchResultItem을 반환하는 JSX코드입니다.

Item을 묶는 리스트를 하나 만들어서 가둬 놓자니 순회한 배열의 요소가 갖는 타입이 모두 상이했습니다.

SearchResultItem에 전달해야 하는 props값도 페이지마다, 데이터마다 상이하여 섣불리 묶었다간 결합도만 높아지겠다 싶어 List를 만들지 않고 SearchResultItem을 필요할 때 사용하도록 구현했었습니다.

우선 큰 문제는 한 가지라고 생각했습니다.

1. list에 무엇이 들어올지 모르는데 어떻게 수정할 수 있지? : typescript의 제네릭 타입과 renderProps로 확장성을 높일 수 있겠다 생각했습니다.

 

SearchResult.tsx

...

interface SearchResultListProps<T> {
  list: T[];
  render: (item: T, index: number) => JSX.Element;
}

const SearchResultList = <T,>({list, render}: SearchResultListProps<T>) => {
  return list.map((item, index) => render(item, index));
};

SearchResult.List = SearchResultList;

export default SearchResult

SearchResultList를 구현하고, typescript 제네릭을 통해 list에 들어오는 배열의 요소 타입을 갖는 props 인터페이스를 선언하였습니다.

이렇게 되면 타입스크립트는 자동으로 render함수의 첫 번째 인지는 list 요소의 타입인 것으로 정의되어 개발 편의성을 보장하며, render props패턴의 컴포넌트를 사용할 수 있게 됩니다!

실제로 사용하면 아래와 같이 수정할 수 있습니다.

수정 부분 (SearchResultItem의 리팩토링은 별도로 이루어졌습니다)

SearchResult.List로 SearchResult.tsx에 정의한 리스트 컴포넌트를 불러오고, list prop에 배열을 넣은 뒤 render prop을 통해 렌더링부분을 정의해줍니다. 
기존 map으로 사용되던 콜백 함수 부분을 그대로 모델링 하였다고 생각하면 이해가 빠를 것 같습니다.

(SearchResultItem 컴포넌트 또한 새로이 리팩토링되어 list와 render prop부분만 봐주시면 좋을 듯 합니다)

render prop의 첫 번째 인자의 타입을 확인해보면 togetherZones 요소의 타입이 그대로 정의되어 있는 것을 확인할 수 있습니다.

이렇게 자유롭게 list를 통해 렌더링되던 SearchResult 관련 코드의 상당 부분을 수정할 수 있었습니다.

다음엔 다른 디자인 패턴으로 찾아오겠습니다

반응형