[Typescript] 부모에서 자식 컴포넌트의 Props에 Type 전달하기

Jiwoo JEONG·2022년 8월 24일
1
post-thumbnail

문제발생

enum값만 받으면 enum의 길이만큼 Tab Item을 만들 수 있도록 사용할 수 있는 Tab이라는 공용 컴포넌트를 만들고 싶었다.

관심사를 분리(SoC)하기 위하여 tab을 사용할 부모 컴포넌트에서 선택된 값을 concern하기 위해 사용할 useTab이라는 커스텀 훅을 작성하였다.

  • useTab에서는 선택된 tab의 값을 state로 가진다.
  • useTab은 type T를 전달받아 state의 type을 지정한다
  • useTab은 initialValue를 받아 state를 초기화한다.
const useTab = <T>(initialValue: T) => {
  const [selectedTab, setSelectedTab] = useState<T>(initialValue);

  const handleSelectedTab = (tab: T) => {
    setSelectedTab(tab);
  };

  return {
    selectedTab,
    handleSelectedTab,
  };
};

export default useTab;

Tab 컴포넌트에서는 선택된 값과 state를 바꾸는 함수를 전달 받아 화면에 그려주고, interact(클릭 이벤트)에 대해 concern한다.

  • Tab 사용할 enum을 전달 받아 배열화하여 TabItem만큼 반복문을 실행한다.
  • TabItem 클릭 이벤트 시, enum의 value를 state 바꾸는 함수에 전달한다.
interface Props {
  tabEnum: Record<string, string>;
  tabItemWidth?: number;
  selectedTab: string;
  handleSelectedTab: (tab: string) => void;
}

const Tab: React.FC<Props> = props => {
  const { tabEnum, tabItemWidth, selectedTab, handleSelectedTab } = props;
  const enumArray = objectToArray(tabEnum); //enum을 array로 만드는 util

  return (
    <Wrapper>
      {enumArray.map(([key, value]) => (
        <TabItem
          key={key}
          enumKey={key}
          enumValue={value as string}
          tabItemWidth={tabItemWidth}
          isSelected={value === selectedTab}
          handleSelectedTab={handleSelectedTab}
        />
      ))}
    </Wrapper>
  );
};

const Wrapper = styled(Row)`
  width: 100%;
  justify-content: flex-start;
  align-items: center;
`;

문제

위 코드에서 문제는 부모 컴포넌트에서 TabhandleSelectedTab을 전달하고자 할 때 발생하였다.

  • useTab에서 지정해준 type인 TTab컴포넌트의 handleSelectedTab의 props인 string의 type이 맞지 않아서 typescript 에러가 발생하였다.

해결

  1. 가장 쉽게 해결할 수 있는 방법은 handleSelectedTab의 props의 type을 any로 하는 것이다.
    • 하지만 이 방법은 굉장히 찝찝함을 가져다 주었고, any type을 사용하고 싶지 않았다. ❌

따라서 "Tab을 사용하는 부모 컴포넌트에서 Tab 컴포넌트로 type을 전달할 수 없을까?"라는 생각을 하였다.

그래서 아래와 같이 수정하게 되었다.

interface Props<T> {
  tabEnum: T;
  tabItemWidth?: number;
  selectedTab: T;
  handleSelectedTab: (tab: T) => void;
}

export type TabComponentInterface<T = any> = React.FC<Props<T>>; //추가

const Tab: TabComponentInterface = props => {
  const { tabEnum, tabItemWidth, selectedTab, handleSelectedTab } = props;
  const enumArray = objectToArray(tabEnum); //enum을 array로 만드는 util

  return (
    <Wrapper>
      {enumArray.map(([key, value]) => (
        <TabItem
          key={key}
          enumKey={key}
          enumValue={value as string}
          tabItemWidth={tabItemWidth}
          isSelected={value === selectedTab}
          handleSelectedTab={handleSelectedTab}
        />
      ))}
    </Wrapper>
  );
};

const Wrapper = styled(Row)`
  width: 100%;
  justify-content: flex-start;
  align-items: center;
`;

export default Tab;
  • interface Props를 Type을 전달 받아 interface 내에서 사용할 수 있도록 하였다.
  • TabComponentInterface type을 만들어 Type을 전달 받을 수 있도록 하고, export 하여 부모 컴포넌트에서도 사용할 수 있게 하였다.
  • Tab 컴포넌트를 TabComponentInterface type으로 지정하였다.
const TabComponent = Tab as TabComponentInterface<TempEnum>;
...
const Presenter: React.FC<Props> = props => {
	...
    return(
    	...
        <TabComponent
        	tabEnum={TempEnum}
            handleSelectedTab={handleSelectedTab}
            selectedTab={selectedTab}
        />
        ...
    )
}
  • Tab 컴포넌트를 as TabComponentInterface<TempEnum>으로 TempEnum type을 전달하도록 type 지정을 하였고, TabComponent를 실제 return에서 JSX.element로 사용하였다.
profile
FE Developer as Efficiency Maker

0개의 댓글