리액트 컴포넌트의 타입
리액트로 타입스크립트로 작성할 때 @types/react 패키지에 정의된 내장 타입을 사용한다. 이중에서 헤깔릴 수 있는 타입도 존재하고 그에 대한 유의점을 알려준다.
1. 클래스 컴포넌트 타입
작년부터 리액트를 시작해서, 클래스 컴포넌트를 사용해본적이 없다. 하지만 아래의 예시를 통해 클래스 컴포넌트 타입에서 타입을 쓰는 것을 확인할 수 있다.
import { ComponentLifecycle } from 'react';
interface Component<P = {}, S = {}, SS = any>
extends ComponentLifecycle<P, S, SS> {}
class Component<P, S> {}
class PureComponent<P = {}, S = {}, SS = any> extends Component<P, S, SS> {}
interface WelcomProps {
name: string;
}
class Welcome extends React.Component<WelcomProps> {
// 생략
}
P와 S는 각각 props와 state이다.
2. 함수 컴포넌트 타입
//함수 선언을 사용
function Welcome(props: WelcomeProps): JSX.Element {
return <div></div>;
}
// 함수 표현식을 사용한 방식 3가지
const Welcome2: React.FC<WelcomeProps> = ({ name }) => {
return <div></div>;
};
const Welcome3: React.VFC<WelcomeProps> = ({ name }) => {
return <div></div>;
};
const Welcome4 = ({ name }: WelcomeProps): JSX.Element => {
return <div></div>;
};
FC는 FunctionComponent 의 약자이고, VFC는 children이 없는 FC이다. 18v에서는 VFC가 사라지고, FC의 기본 옵션이 children 있음에서 없음으로 변경되었다.
3. Children props 타입 지정
가장 보편적인 children의 타입은 ReactNode | undefined가 된다.
ReactNode 타입은 ReactElement 외에도 boolean, number 등 여러 타입을 포함하고 있는 타입이다.
세분화하고 싶으면 아래와 같이 사용가능하다.
type SpecificProps = {
children: '천생연분' | '더 귀한 분' | '귀한 분' | '고마운 분';
};
type StringProps = {
children: string;
};
type ReactElementProps = {
children: ReactElement;
};
4. render 메서드와 함수 컴포넌트의 반환 타입-React.ReactElement, JSX.Element, React.ReactNode
React.ReactElement 와 JSX.Element , React.ReactNode 타입은 헷깔리기 쉽다.
3가지 모두 리액트의 요소를 나타내는 타입인데, 차이점이 존재한다.
JSX.Element < React.ReactElement < React.ReactNode ( 포함관계)
JSX.Element
declare global {
namespace JSX {
interface Element extends React.ReactElement<any, any> {}
}
}
JSX.Element 타입은 위의 코드를 보면 알 수 있듯이 ReactElement를 확장하고 있는 타입이며, 글로벌 네임스페이스에 정의되어 있어 외부 라이브러리에서 컴포넌트 타입을 재정의 할 수 있는 유연성을 제공한다.
JSX.Element는 ReactElement의 특정 타입으로 props와 타입 필드를 any 로 가지는 타입이다.
리액트 엘리먼트를 prop으로 전달받아 render props 패턴으로 컴포넌트를 구현할 때 유용하다.
interface Props {
icon: JSX.Element;
}
const Item = ({ icon }: Props) => {
const iconSize = icon.props.size;
return <li>{icon}</li>;
};
const App = () => {
return <Item icon={<Icon size={14} />} />;
};
React.ReactElement
React. ReactElement 는 리액트 컴포넌트를 객체 형태로 저장하기 위한 포맷이다.
interface ReactElement<
P = any,
T extends string | JSXElementConstructor<any> =
| string
| JSXElementConstructor<any>
> {
type: T;
props: P;
key: Key | null;
}
ReactElement 타입은 JSX의 createElement 메서드 호출로 생성된 리액트 엘리먼트를 나타내는 타입이다.
ReactElement의 제네릭으로 컴포넌트의 props를 지정해 줄 수 있다.
React.ReactNode
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
type ReactFragment = {} | Iterable<ReactNode>;
type ReactNode =
| ReactChild
| ReactFragment
| ReactPortal
| boolean
| null
| undefined;
ReactNode는 리액트의 render함수가 반환할 수 있는 모든 형태를 담고 있다.
따라서 prop으로 리액트 컴포넌트가 다양한 형태를 가질 수 있게 하고 싶을 때 유용하게 사용된다.
5. 리액트에서 기본 HTML 요소 타입 활용하기
HTML 태그의 속성 타입을 활용하는 대표적인 2가지 방법은 DetailedHTMLProps 와 ComponentWithoutRef가 있다.
차이점은 ref의 포함 유무이다.
DetailedHTMLProps
type NativeButtonProps = React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>;
type ButtonProps = {
onClick?: NativeButtonProps['onClick'];
};
ComponentWithoutRef
type NativeButtonType2 = React.ComponentPropsWithoutRef<'button'>;
type ButtonProps2 = {
onClick?: NativeButtonType2['onClick'];
};
최근에는 함수 컴포넌트를 많이 쓴다. 이때 함수 컴포넌트의 props로 DetailedHTMLProps와 같이 ref를 포함하는 타입을 사용하게 되면, 실제로는 동작하지 않는 ref를 받도록 타입이 지정되어 예기치 않은 에러가 발생할 수 있다
따라서 HTML 속성을 확장하는 props를 설계할 때는 ComponentPRopsWithoutRef 타입을 사용하여 ref가 실제로 forwardRef와 함께 사용할 때만 props로 전달되도록 타입을 정의하는 것이 안전하다.
'독서' 카테고리의 다른 글
우아한 타입스크립트 with React - 9장 훅 (0) | 2024.03.14 |
---|---|
우아한 타입스크립트 with React -8장 JSX에서 TSX로 - 2 (0) | 2024.03.09 |
우아한 타입스크립트 with React -7장 비동기 호출 -2 (0) | 2024.02.26 |
우아한 타입스크립트 with React -7장 비동기 호출 -1 (0) | 2024.02.25 |
우아한 타입스크립트 with React -6장 타입스크립트 컴파일 (0) | 2024.02.19 |