여기 Chip라는 커스텀 컴포넌트가 있다.
// 속성을 간결하게 표시할때 사용하는 Chip 컴포넌트
<Chip>내용</Chip>
이 Chip 컴포넌트를 구현하며 variant라는 prop을 통해 Chip의 배경색과 글자색을 지정해주는 일종의 프리셋을 구현했다.
<Chip variant="default | positive | negative">내용</Chip>
끝이 아니다. 사실 이 컴포넌트는 background-color와 text-color도 prop으로 받을 수 있다.
그러면 아래와 같이 컴포넌트를 사용할 수 있게 된다.
<Chip variant="positive" backgroundColor="#eeeeee" />
<Chip variant="negative" textColor="#aaaaaa" />
<Chip variant="default" backgroundColor="#eeeeee" textColor="#aaaaaa" />
이 코드를 처음 보는 사람은 어떻게 생각할까.
왜 variant prop은 따로 존재하며, variant와 backgroundColor 혹은 textColor가 동시에 적용되었을때 어떤게 적용될것인가 등등...
결국 Chip 컴포넌트를 열어보는것을 참지 못하고 분석하기 시작할 것이다.
이 문제를 해결하기 위해서, Chip 컴포넌트에서는 backgroundColor와 textColor만 받고
그 상위 컴포넌트로 PresetChip를 분리하는 방법도 생각해보았다.
하지만 개인적으론 관리해야될 비슷한 컴포넌트가 많아지는것은 불호하기 때문에
(관리해야될 컴포넌트 증가, 공통 컴포넌트간 의존성 발생등)
이 글의 주제와 같이 타입스크립트를 이용해 variant을 받으면 backgroundColor와 textColor를 prop으로 받지 못하게 할 수 없을까 하는 생각으로 이어지게 되었다.
조건부로 prop 받기
아래와 같은 형태로 코드를 구현할 수 있었다.
type DefaultProps = { className?: string; children?: string | React.ReactNode };
type PresetProps = {
variant?: "default" | "positive" | "negative";
textColor?: never;
backgroundColor?: never;
};
type CustomProps = {
variant?: never;
textColor?: string;
backgroundColor?: string;
};
export type ChipProps = (DefaultProps & PresetProps) | (DefaultProps & CustomProps);
const Chip = ({
className,
variant,
color,
backgroundColor,
children,
}: ChipProps) => {...}
간단하게 설명하면 조건부로 나뉘는 프롭 영역의 타입을 분리했다.
우선 DefaultProps는 기본적으로 필요한 속성들을 정의해둔 타입이다. 즉, 조건부로 받을 필요없는 프롭들의 타입을 선언하는 곳이다.
PresetProps는 variant 프롭을 받는 경우, textColor와 backgroundColor를 사용하지 않기에 never 로 지정.
CustomProps는 반대로, textColor 혹은 backgroundColor를 받으면 variant는 사용되지 않기에 never로 지정한다.
그리고 ChipProps는 조건부 타입을 조합한 타입이다.
위와 같은 식으로 Chip 컴포넌트의 타입을 선언하고 사용하려 하면 아래와 같이 typeError가 발생하게 된다.
Type '{ children: string; variant: "positive"; backgroundColor: string; color: string; }' is not assignable to type 'IntrinsicAttributes & TagProps'.
Type '{ children: string; variant: "positive"; backgroundColor: string; color: string; }' is not assignable to type 'CustomProps'.
Types of property 'variant' are incompatible.
Type '"positive"' is not assignable to type 'undefined'.ts(2322)
추가
사실은 이때 발생하는 에러메세지도 핸들링하고 싶었지만 불가능했다.
(대충 타입스크립트 타입 체크는 타입스크립트 컴파일러로 이루어지며 그에 따른 메세지 텍스트는 내장되어있기 때문)
그러면 위와 같이 차라리 타입을 이용해 조건부 처리를 하는게 아니라,
일반적으로 prop를 받아 내부에서 아래와 같이 조건문을 작성하고, JSDoc을 이용해 '@throws {Error} 에러메세지' 를 작성하는 방식으로 에러메세지도 보여주는게 더 효과적이지 않을까 하는 생각으로 이어졌다. (타입가드의 개념)
if (variant && (color || backgroundColor)) {
throw new Error('default 태그는 color, backgroundColor를 사용할 수 없습니다.');
}