Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags more
Archives
Today
Total
관리 메뉴

개발의변화

우아한타입스크립트 8장 리액트 컴포넌트 타입 본문

카테고리 없음

우아한타입스크립트 8장 리액트 컴포넌트 타입

refindmySapporo 2023. 11. 23. 17:30
반응형

8.1 리액트 컴포넌트 타입

  • 리액트 애플리케이션을 타입스크립트 작성할 때 @types/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> {}

P,S는 props와 상태를 의미
props와 state를 제네릭으로 받는 걸 알고 잇음

interface WelcompeProps {
    name: string;
}

class Welcome extends React.Component<WelcomeProps> {

}

함수 컴포넌트 방식

// 함수 선언을 사용한 방식
function Welcome(props: WelcomeProps): JSX.Element {}

// 함수 표현식을 사용한 방식 - React.FC 사용
const Welcome: React.FC<WelcomeProps> = ({ name }) => {};

// 함수 표현식을 사용한 방식 - React.VFC 사용
const Welcome: React.VFC<WelcomeProps> = ({ name }) => {};

// 함수 표현식을 사용한 방식 - JSX.Element를 반환 타입으로 지정
const Welcome = ({ name }: WelcomeProps): JSX.Element => {};

type FC<P = {}> = FunctionComponent<P>;

interface FunctionComponent<P = {}> {
  // props에 children을 추가
  (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
  propTypes?: WeakValidationMap<P> | undefined;
  contextTypes?: ValidationMap<any> | undefined;
  defaultProps?: Partial<P> | undefined;
  displayName?: string | undefined;
}

type VFC<P = {}> = VoidFunctionComponent<P>;

interface VoidFunctionComponent<P = {}> {
  // children 없음
  (props: P, context?: any): ReactElement<any, any> | null;
  propTypes?: WeakValidationMap<P> | undefined;
  contextTypes?: ValidationMap<any> | undefined;
  defaultProps?: Partial<P> | undefined;
  displayName?: string | undefined;
}

React.FC, React.VFC로 보통 함수 컴포넌트를 선언할 때 사용함
FC는 FunctionComponent의 약자로 React.FC와 React.VFC는 리액트에서 함수 컴포넌트의 타입 지정을 위해 제공되는 타입
React.FC와 React.VFC 차이는 childred이라는 타입을 허용하는지 아닌지에 대한 차이

React.FC는 암묵적으로 children을 포함하고 있기 때문에 해당 컴포넌트에서 children을 사요ㅗㅇ하지 않더라도 children props를 허용한다.
따라서 children props가 필요하지 않은 컴포넌트에서는 React.VFC를 많이 사용한다

하지만 리액트 v18로 넘어오면서 React.VFC가 삭제되고 React.FC에서 children이 사라졌다.
그래서 앞으로는 React.VFC 대신 React.FC 또는 props 타입반환타입을 직접 지정하는 형태로 타이핑

children props 타입 지정

children 타입은 ReactNode | undefined가 된다. ReactNode는 ReactElement외에도 boolean,number 등 여러 타입을 포함하고 있는 타입
예를 들어 특정 문자열만 허용하고 싶을 때는 children에 대해 추가로 타이핑해줘야한다.

// example 1
type WelcomeProps = {
  children: "천생연분" | "더 귀한 분" | "귀한 분" | "고마운 분";
};

// example 2
type WelcomeProps = { children: string };

// example 3
type WelcomeProps = { children: ReactElement };

render 메서드와 함수 컴포넌트의 반환 타입 - React.ReactElement vs JSX.Element vs React.ReactNode

interface ReactElement<
  P = any,
  T extends string | JSXElementConstructor<any> =
    | string
    | JSXElementConstructor<any>
> {
  type: T;
  props: P;
  key: Key | null;
}

React.createElement로 변환하면 반환타입은 ReactElement
리액트는 실제 DOM이 아니라 가상의 DOM을 기반으로 렌더링하는데 가상 DOM의 엘리먼트는 ReactElement 형태로 저장
ReactElement타입은 리액트 컴포넌트를 객체형태로 저장하기 위한 포맷

declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> {}
  }
}

JSX.Element는 리액트의 ReactElement를 확장하고 있는 타입

범위가 즉 ReactNode > ReactElement > JSX.Element로 되어있다.

5. ReactElement, ReactNode, JSX.Element 활용하기

ReactElement, ReactNode, JSX.Element는 모두 리액트의 요소를 나타내는 타입

declare namespace React {
  // ReactElement
  interface ReactElement<
    P = any,
    T extends string | JSXElementConstructor<any> =
      | string
      | JSXElementConstructor<any>
  > {
    type: T;
    props: P;
    key: Key | null;
  }

  // ReactNode
  type ReactText = string | number;
  type ReactChild = ReactElement | ReactText;
  type ReactFragment = {} | Iterable<ReactNode>;

  type ReactNode =
    | ReactChild
    | ReactFragment
    | ReactPortal
    | boolean
    | null
    | undefined;
  type ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P>;
}

// JSX.Element
declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> {
      // ...
    }
    // ...
  }
}

리액트 엘리먼트를 생성하는 createElement 메서드에 대해 들어본 적이 있을 것이다.
리액트를 사용하면서 JSX는 craeteElement메서드를 호출하기 위한 문법이다.

JSX: 리액트에서 UI를 표현하는데 사용, 리액트 컴포넌트를 선언하고 사용할 때 더욱 간결하고 가독성 있게 코드를 작성할 수 있도록 도와준다. 또한 HTML과 유사한 문법을 제공하여 리액트 사용자에게 렌더링 로직을 쉽게 만들어준다.

const element = React.createElement(
  "h1",
  { className: "greeting" },
  "Hello, world!"
);

// 주의: 다음 구조는 단순화되었다
const element = {
  type: "h1",
  props: {
    className: "greeting",
    children: "Hello, world!",
  },
};

declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> {
      // ...
    }
    // ...
  }
}

ReactElement 타입은 JSX의 createElement 메서드 호출로 생성된 리액트 엘리먼트를 나타내는 타입이라고 할 수 있다.

ReactNode

type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
type ReactFragment = {} | Iterable<ReactNode>; // ReactNode의 배열 형태
type ReactNode =
  | ReactChild
  | ReactFragment
  | ReactPortal
  | boolean
  | null
  | undefined;

ReactChild외에도 boolean,null,undefined 등 훨씬 넓은 범주의 타입 포함, ReactNode는 리액트의 render함수가 반환할 수 있는 모든 형태

JSX.Element

declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> {
      // ...
    }
    // ...
  }
}

JSX.Element는 ReactElement의 제네릭으로 props와 타입필드에 대해 any타입을 가지도록 확장

사용예시

ReactNode: 리액트의 render함수가 반환할 수 잇는 모든 형태를 담음, composition을 활요하기 위해 children을 사용
string,number,null,undefined등 어떤 타이든 children prop으로 지정할 수 있게 하고 싶다면 ReactNode 타입으로 children 선언

type PropsWithChildren<P = unknown> = P & {
  children?: ReactNode | undefined;
};

interface MyProps {
  // ...
}

type MyComponentProps = PropsWithChildren<MyProps>;

JSX.Element

props와 타입 필드가 any 타입인 리액트 엘리먼트를 나타낸다.
리액트 엘리먼트를 prop으로 전달받아 Render props 패턴으로 컴포넌트를 구현할 떄 유용하다.

type PropsWithChildren<P = unknown> = P & {
  children?: ReactNode | undefined;
};

interface MyProps {
  // ...
}

type MyComponentProps = PropsWithChildren<MyProps>;

해당 prop에는 jsx문법만 삽입 가능

ReactElement: 추론 관점에서 더 유용하게 활용 가능, 어던 props가 있는지가 추론되어 IDE에 표시되는 것 확인

interface IconProps {
  size: number;
}

interface Props {
  // ReactElement의 props 타입으로 IconProps 타입 지정
  icon: React.ReactElement<IconProps>;
}

const Item = ({ icon }: Props) => {
  // icon prop으로 받은 컴포넌트의 props에 접근하면, props의 목록이 추론된다
  const iconSize = icon.props.size;

  return <li>{icon}</li>;
};

기본 HTML 요소 타입 활용

새로운 기능이나 UI 추가할 떄 활용

HTML 태그 속성 타입 활용하는 대표적인 2가지 방법
DetailedHTMLProps, ComponentWithoutRef

DetaildHTMLProps

type NativeButtonProps = React.DetailedHTMLProps<
  React.ButtonHTMLAttributes<HTMLButtonElement>,
  HTMLButtonElement
>;

type ButtonProps = {
  onClick?: NativeButtonProps["onClick"];
};

ButtonProps의 onClick 타입은 HTML button 태그의 onClick 이벤트 핸들러 타입과 동일하게 할당되는 것을 확인

// forwardRef를 사용해 ref를 전달받을 수 있도록 구현
const Button = forwardRef((props, ref) => {
  return (
    <button ref={ref} {...props}>
      버튼
    </button>
  );
});

// buttonRef가 Button 컴포넌트의 button 태그를 바라볼 수 있다
const WrappedButton = () => {
  const buttonRef = useRef();

  return (
    <div>
      <Button ref={buttonRef} />
    </div>
  );
};
type NativeButtonType = React.ComponentPropsWithoutRef<"button">;

// forwardRef의 제네릭 인자를 통해 ref에 대한 타입으로 HTMLButtonElement를, props에 대한 타입으로 NativeButtonType을 정의했다
const Button = forwardRef<HTMLButtonElement, NativeButtonType>((props, ref) => {
  return (
    <button ref={ref} {...props}>
      버튼
    </button>
  );
});

ComponentPropsWithoutRef x타입을 사용하여 ref가 시제로 forwardRef와 함께 사용될 때만 Props로 전달되도록 타입을 정의하는 것이 안전하다.

8.2 타입스크립트로 리액트 컴포넌트 만들기

JSX로 구현된 Select 컴포넌트

JSDOCS로 컴포넌트에 대한 설명과 각 속성이 어떤 역할을 하는지 간단하게 알려줄 수 있다.

const Select = ({ onChange, options, selectedOption }) => {
  const handleChange = (e) => {
    const selected = Object.entries(options).find(
      ([_, value]) => value === e.target.value
    )?.[0];
    onChange?.(selected);
  };

  return (
    <select
      onChange={handleChange}
      value={selectedOption && options[selectedOption]}
    >
      {Object.entries(options).map(([key, value]) => (
        <option key={key} value={value}>
          {value}
        </option>
      ))}
    </select>
  );
};

props 인터페이스를 적용하여 타이입을 예측할 수 있다.
리액트는 이벤트들을 따로 관리하기에 이에 따라 타입을 일치시킨다.

JSDocs로 일부 타입 지정하기

/**
* Select 컴포넌트
* @param {Object} props - Select 컴포넌트로 넘겨주는 속성
* @param {Object} props.options - { [key: string]: string } 형식으로 이루어진 option 객체
* @param {string | undefined} props.selectedOption - 현재 선택된 option의 key값 (optional)
* @param {function} props.onChange - select 값이 변경되었을 때 불리는 callBack 함수 (optional)
* @returns {JSX.Element} 
*/
const Select = //...

props인터페이스 적용하기

oprtions가 어떤 형식의 객체를 나타내는지나 onChange의 매개변수 및 반환 값에 대한 정보를 알기 쉬빚않아서 잘못된 타입 전달될 수 있는 위험 존재

반응형