본문 바로가기

React.JS

공통컴포넌트 any 대신 제네릭을 사용해볼까?

아래와 같이 공통 컴포넌트에서 onSelect의 param을 any로 정의하는 것에 대한 문제를 해결하고 싶었다.

// SelectField.tsx (공통 컴포넌트)
interface Props {
  ...
  onSelect?: (param: any) => void;
}

const SelectField = (props: Props) => {
	return(
    <Select onSelect={onSelect} />
  )
}

 

첫번째 방법 (Not OK)

  • onSelect의 제네릭 타입을 param에 넘겨주는 방법을 시도 했다.
  • 에러는 없으나 Form.tsx에서 제네릭이 any로 추론되어 적용되었기 때문에 올바른 방법이 아니다.
  • 또한 onSelectSettlementPeriodType라는 함수를 실행할때에는 타입을 넣어주어서 의미가 있겠지만 props로 넘겨만 주고 실행은 antd에서 알아서 하기 때문에 이렇게 제네릭을 사용할 필요는 없었다.
// SelectField.tsx
interface Props {
  ...
  onSelect?: <T,>(param: <T>) => void;
}

const SelectField = (props: Props) => {
	return(
    <Select onSelect={onSelect} />
  )
}
// Form.tsx
const onSelectSettlementPeriodType = <SettlementPeriodType,>(selectedValue?: SettlementPeriodType) => {
    if (selectedValue === "SettlementPeriodType이 아닌 값") {
      // 에러 발생하지 않는다. 
      // selectedValue가 any로 추론되기 때문에 선언부만 필요한 함수의 경우 의미가 없는 제네릭이다.
      // selectedValue에 뭐를 넣어도 타입 에러가 발생하지 않는다.
      // SettlementPeriodType를 T로 바꿔서 넣은 것도 마찬가지인데 착각을 함
    }
  };

return(
  <SelectField
      onSelect={onSelectMarketingExpenseSettlementType}
  />
)

 

두번째 방법 (OK)

  • 제네릭 인터페이스 적용
  • Form.tsx에서 MarketingExpenseSettlementType을 제네릭으로 넘겨주고 SelectField.tsx의 Props interface에서 onSelect의 param 타입으로 받아온 제네릭을 넘겨서 타입을 정의했다.
  • 이렇게 하면 onSelectMarketingExpenseSettlementType 함수 선언부에서 MarketingExpenseSettlementType만 param으로 받아오게 강제하기 때문에 제네릭을 사용한 효과가 있다.
// SelectField.tsx
interface Props<T> {
	onSelect?: (param: T) => void;
}

const SelectField = <T,>(props: Props<T>) => {
	 return(
     <Select onSelect={onSelect} />
   )
}

// Form.tsx
...
const onSelectSettlementPeriodType = (selectedValue?: SettlementPeriodType) => {
    if (selectedValue === "SettlementPeriodType가 아닌 타입") {
	     // MarketingExpenseSettlementType이 아니기 때문에 에러 발생
			 // 제네릭으로 넘겨준 타입이 잘 동작하였다!
    } 
  };

return(
  <SelectField<MarketingExpenseSettlementType> // 이렇게 제네릭을 넘겨줄 수 있다.
     onSelect={onSelectMarketingExpenseSettlementType}
  />
 )

 

세번째 방법 (OK)

  • antd의 SelectProps에게 타입을 맡기고 onSelect의 타입을 정의 하지 않는다.
  • SelectProps 내부에 onSelect의 타입이 정의가 되어있다.
  • 하지만 내부에서 onSelect의 param은 any로 되어 있음을 참고해야 한다.
// SelectField.tsx
import { Select, SelectProps } from 'antd';

interface Props {
	... 
  // onSelect의 타입을 따로 정의하지 않고 antd SelectProps에 맡김
}

type SelectFieldProps = Props & SelectProps;

const SelectField = (props: SelectFieldProps) => {
  return(
    <Select onSelect={onSelect} ... />
  )
}

 

요약

  • 공통 컴포넌트에서 명시적으로 제네릭을 활용하여 props의 타입정의를 하고 싶으면 두번째 방법을 사용하면 어떨까 알아 보았다. 더 좋은 방법이 있을 수도 있다.
  • 라이브러리에서 미리 제공하는 타입이 있다면 사용해도 된다. 내부적으로는 어떤 타입인지를 알고 있으면 될 것 같다.

헷갈렸던 부분

  • onSelect가 제네릭으로 타입 정의되어 있더라도 별도로 실행을 시키는 곳은 없다. 즉 함수 실행과 동시에 타입을 제네릭으로 넣어주는 부분도 없기 때문에 onSelect함수를 꼭 제네릭으로 선언하여 props로 넘겨줄 필요가 없었다.