728x90

리액트 컴포넌트의 타입

리액트로 타입스크립트로 작성할 때 @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로 전달되도록 타입을 정의하는 것이 안전하다.

+ Recent posts