TypeScript로 React 다형성 컴포넌트 만들기

seonja kim·2022년 1월 22일
12

해당 글은 Iskander Samatov님의 React polymorphic components with TypeScript를 번역한 글입니다. 원문은 위 링크를 통해서 보실 수 있으며, 오역은 댓글로 남겨주시면 바로 반영하도록 하겠습니다.

다형성 컴포넌트는 널리 사용되는 React 패턴입니다. 들어본 적이 없더라도 코드에서 접했을 가능성이 큽니다. 간단히 설명하면 이 패턴은 as prop을 이용해 컴포넌트가 어떤 HTML 태그를 렌더링할 것인지 지정할 수 있습니다.

다형성 컴포넌트의 유연성을 오남용하는 실수를 없애기 위해 TypeScript의 도움을 받을 수 있습니다. TypeScript와 제네릭을 사용하여 타입을 적용한 다형성 컴포넌트를 작성하는 방법에 대해 살펴보겠습니다.

다형성 컴포넌트 개요

먼저 다형성 컴포넌트를 사용하는 방법을 살펴보겠습니다. 당신에게 HTML 링크가 걸려있는 Button 컴포넌트가 있다고 가정합시다. 만약 Button이 다형성 컴포넌트라면 당신은 이런식으로 작성할 수 있습니다.

버튼은 a태그로 렌더될 것이고 href같은 a태그의 props를 동일하게 전달받을 수 있습니다.

간단한 구현

이제 직접 구현하면서 살펴보겠습니다. 다형성 컴포넌트라고 하지만 타입 검사 없이는 그저 기초적인 구현일 뿐입니다.

위 예시에서는 type을 any로 지정하여 type 검사를 하지 않았습니다.

아래의 라인이 위에서 구현한 패턴이 작동하는 이유입니다.

const Component = as || 'button';

우리는 as prop으로 넘겨주는 값에 따라 컴포넌트를 렌더링하거나 기본으로 설정해 둔 button 태그로 렌더링하게 됩니다.

이 접근방식의 문제점은 사용자가 잘못된 props를 넘기지 못하게 하는 장치가 없다는 것입니다.

위 예시에서 우리는 as prop을 넘기지 않은 상태로 a태그에 속하는 href 속성을 사용했습니다. 타입스크립트를 사용할 경우 오류를 즉각적으로 알려주어 우리가 이와 같은 에러를 작성하지 않도록 도와줍니다.

TypeScript를 통한 타입 검사

다음 단계는 컴포넌트의 prop 타입 검사를 강화하는 것입니다.

위의 코드는 제네릭을 소개합니다. 다음 줄에서 구성 요소를 제네릭으로 만들었습니다.

const Button = <T extends ElementType = "button">

ElementType 은 React의 유틸리티 유형입니다. 제네릭 매개변수 T를 ElementType으로 설정하여 우리의 버튼이 HTML 태그와 React 컴포넌트 유형만 전달받도록 합니다.

이전에 수동으로 지정하던 것과 달리 TMyButtonProps 타입으로 넘겨서 사용합니다. MyButtonProps 내부에는 as prop을 T타입으로 정하여 모두 자연스럽게 연결되게 합니다.

Button props의 마지막 타입은 MyButtonProps<T> & ComponentPropsWithoutRef<T>입니다. 이 타입은 우리가 수동으로 지정한 props와 ComponentPropsWithoutRef의 조합입니다.
ComponentPropsWithoutRef는 React 컴포넌트의 기본 props 세트를 포함하고 있는 React 라이브러리에서 가져다 사용할 수 있는 타입입니다.

이 시점에서 Button 컴포넌트는 as에 부여된 태그에 기반하여 동적으로 props를 계산할 수 있게 됩니다. 이전의 사용자 예제를 다시보면 다음과 같이 오류가 표시됩니다.

오류 메시지가 복잡하지만 중요한 부분은 다음입니다: Property 'href' does not exist on type 'IntrinsicAttributes & MyButtonProps<"button">.

우리의 Button 컴포넌트는 링크로 렌더링되고 있지 않기때문에 더이상 href 속성을 받아들아들이지 않습니다. 우리가 as="a"를 추가하면, 오류도 사라집니다.

이름 충돌 피하기

우리의 컴포넌트는 지금도 충분해보이지만 조정할 사항이 한 가지 더 있습니다. ComponentPropsWithoutRef에서 제공되는 props와 우리가 수동으로 넘겨주는 props 사이에 이름 충돌이 없는지 확인하고 싶습니다. 이름 충돌은 이해하기 어려운 TypeScript 경고를 발생시킬 수 있습니다.

오류를 수정하는 것은 간단합니다: Omit을 사용하여 컴포넌트 props의 최종 타입을 조정하는 겁니다.

OmitComponentPropsWithoutRef에서 MyButtonProps의 prop key들을 제외하여 새로운 타입을 구성하는 타입스크립트 유틸리티입니다. Omit을 사용하여 두 유형 간의 이름 충돌을 방지할 수 있습니다. React에서 유용한 TypeScript 유틸리티 유형 에 대해 자세히 알아보려면 제 포스트를 확인하세요.

이제 Button 컴포넌트가 준비되었습니다!

결론

이 게시물에서는 TypeScript와 제네릭을 사용하여 강타입의 다형성 컴포넌트 작성법을 배웠습니다.

일반적으로 이 접근 방식을 사용하고, React와 함께 TypeScript를 사용하는 것은 더 많은 공수가 요구됩니다. 그러나 코드에서 추가적인 품질보증이 되어 개발 경험을 개선하기 때문에 결국 가치가 있습니다.

읽어 주셔서 감사합니다.

profile
Adventurer

0개의 댓글