리액트에서 prop-types 라이브러리를 사용해 props의 타입을 체크할 수 있다. 그리고 타입스크립트를 사용해 props의 타입을 체크할 수 있다.
같은 일을 하는 것 같지만 다른 일을 한다. 각각 무슨 일을 하는지 알아보고 타입스크립트에서 prop-types를 사용할지 체크해보길 바란다.
타입스크립트
타입스크립트는 동적 언어인 자바스크립트를 정적 언어로 사용하기 위한 언어이다. 정적 언어는 컴파일 시에 타입이 결정되고, 동적 언어는 런타임에 타입이 결정된다.
타입스크립트는 런타임에 타입 체크를 하지 않는다. 이는 타입스크립트 Design Goals 3번 '실행한 프로그램에 런타임 오버헤드를 부과하지 않는다.'에 부합한다.
타입스크립트로 Props의 타입을 체크하기 위해 일반적으로 아래와 같이 작성한다.
interface Props {
title: string;
id: string;
}
function Label({ title, id }: Props) {
return (
<p>
{id}:{title}
</p>
);
}
Label
컴포넌트에 문자열 타입의 title
과 id
를 받도록 지정한 것이다. 그리고 Label
컴포넌트를 사용하겠다.
function Home() {
return (
<>
<Label title='first' id='1' /> // (A)
<Label title='second' id={2} /> // (B)
</>
);
}
(B)에서 오류가 발생하는 것은 분명하다. 이는 컴파일 단계에서 검출될 뿐만 아니라, IDE에서 미리 경고를 한다.
그렇다면 런타임 단계에서 타입 불일치가 발생할 경우는 어떻게 되는 것일까? 이 고민보다 선행돼야 하는 고민은 '어떤 경우에 런타임 단계에서 타입 불일치가 발생할까?'이다. 컴파일 단계를 건너뛰고 런타임에 처음 등장하는 데이터는 외부 데이터뿐이다. API 호출을 통해 받아온 응답 Body, 로컬 스토리지 등의 외부 데이터는 컴파일 단계에서 타입 체크를 할 수 없다. 즉, 외부 데이터는 런타임 단계에서 타입 불일치가 발생할 가능성이 있다.
이것을 증명하기 위해 커피 데이터를 활용해 메뉴판을 만드는 컴포넌트를 작성하겠다. 이 커피 배열의 한 요소의 타입은 아래와 같다.
interface Coffee {
title: string;
id: number;
description: string;
}
하지만 분명 어제까지만 해도 id
의 타입은 string이었고, 수정한 서버 개발자가 이를 알려주지 않아 수정하지 않았다고 가정하겠다. 그렇다면 아래와 같이 컴포넌트를 작성할 수 있다.
interface Coffee {
title: string;
id: string;
}
function Home() {
const [coffees, setCoffees] = useState<Coffee[]>([]);
useEffect(() => {
fetch('https://api.sampleapis.com/coffee/hot')
.then((res) => res.json())
.then((data) => setCoffees(data));
}, []);
return (
<>
{coffees.map(({ title, id }) => (
<Label title={title} id={id} key={id} />
))}
</>
);
}
interface Props {
title: string;
id: string;
}
function Label({ title, id }: Props) {
console.log(typeof id); // ⭐️
return (
<p>
{id}:{title}
</p>
);
}
id
의 타입을 string으로 지정했지만, 실제 타입은 number다. 하지만 아무런 오류도 없다. 컴파일 단계에선 문제가 없었기 때문이다. 언제 터질지 모르는 폭탄이 생겨버린 것이다.
이제 런타임 과정에서 발생하는 타입 불일치를 확인할 방법을 알아보자.
prop-types
Runtime type checking for React props and similar objects.
prop-types 라이브러리는 런타임에 타입 체크를 한다. 이전에 예제에 추가해보겠다.
interface Props {
title: string;
id: string;
}
function Label({ title, id }: Props) {
return (
<p>
{id}:{title}
</p>
);
}
Label.propTypes = {
title: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
};
prop-types는 id
의 실제 타입과 명시한 타입의 불일치를 인지한다. 이를 통해 문제 상황을 조기에 발견했기에 큰 문제로 번지지 않았다.
결론
가정한 상황 이외에도 실제 API 응답 값의 타입과 명시된 타입이 달라질 시나리오는 많다. API 이외에도 로컬 스토리지와 같은 외부 데이터를 런타임에 가져오는 상황이라면 타입 불일치 가능성을 염두에 두길 바란다. 타입스크립트를 사용하는 이유 중 큰 비중을 차지하는 것이 휴먼 에러를 미연에 방지하자는 것이 아닌가? 완벽한 휴먼 에러 퇴치를 원한다면 prop-types 사용을 권장한다.
물론 prop-types 라이브러리로 런타입 타입 체크를 한다면 추가의 개발 리소스를 필요로 하는 것은 사실이다. API의 타입이 바뀔 가능성이 정말 없다면 런타임 타입 체크를 생략하는 것도 방법의 하나다. 그리고 추가적인 리소스를 최소화하기 위해 아래와 같은 도구들이 있다.
brieb/props-type.ts
타입스크립트 유틸리티 타입을 통해 런타임 타입 체크를 하는 구현 방법이다.
해당 구현의 자세한 설명은 Adopting Typescript at Scale를 참고 바란다.
ts-react-loader
props 인터페이스를 기반으로 런타임 타입 체크 코드를 자동으로 생성하는 웹팩 로더이다.
prop-types-ts
타입스크립트 런타임 타입 시스템인 io-ts를 기반으로 제작된 라이브러리이다.
babel-plugin-typescript-to-proptypes
props 인터페이스를 기반으로 런타임 타입 체크 코드를 자동으로 생성하는 바벨 플러그인이다.
참조
https://ko.reactjs.org/docs/typechecking-with-proptypes.html
https://stackoverflow.com/questions/41746028/proptypes-in-a-typescript-react-application
https://github.com/microsoft/TypeScript/wiki/TypeScript-Design-Goals#goals