dwook.record
Published on

타입스크립트 리액트 컴퍼넌트

Authors
  • avatar
    Name
    dwook

Table of Contents

함수형 컴퍼넌트를 작성하는 2가지 방식

1. function 키워드(리액트메뉴얼, 유명개발자)

  • function 키워드를 사용하면 childrend이 없기 때문에 children?: React.ReactNode 작성해줘야 한다.
  • children이 필요없는 컴퍼넌트도 존재한다!
  • function 키워드를 사용한다면, 컴퍼넌트.defaultProps가 제대로 동작.
import React from 'react';

type GreetingsProps = {
  name: string;
mark: string; // ?처리 안해도됨.
}; function Greetings({ name, mark }: GreetingsProps) { return ( <div> Hello, {name} {mark} </div> ); } Greetings.defaultProps = { mark: '!' }; export default Greetings;

2. 화살표함수(React.FC)

  • 장점
    • children props가 자동으로 설정되어서 children?: React.ReactNode 작성하지 않아도 된다.
    • 자동완성 프로퍼티
  • 단점
    • 컴퍼넌트.defaultProps가 제대로 동작하지 않아서 타입정의에 ?를 사용해야한다.
    • 그런데, ?를 사용하면 undefined 타입이 추가되는 상황이 발생!

React.FC를 사용해서 컴퍼넌트를 선언하면, 컴퍼너트.defaultProps가 먹통

import React from 'react';

type GreetingsProps = {
  name: string;
mark?: string; // ?를 붙이면, string 또는 undefined
array?: string[]; }; const Greetings: React.FC<GreetingsProps> = ({ name, mark, array }) => { if(!array) return null; // ?처리를 해서 undefined가 될 수 있기 때문에 처리 필요 return ( <div> Hello, {name} {mark} </div> ); } Greetings.defaultProps = { mark: '!' }; export default Greetings;
// 해결방법. default 값을 파라미터 위치에 넣어주기.
import React from 'react';

type GreetingsProps = {
  name: string;
  mark?: string;
};

const Greetings: React.FC<GreetingsProps> = ({ name, mark = '!' }) => {
  // 이렇게 처리해야 제대로 작동. mark 값이 undefined면, 기본값으로 느낌표.
  return (
    <div>
      Hello, {name} {mark}
    </div>
  );
}

export default Greetings;

컴퍼넌트 작성

import React, { useCallback } from 'react';

// Basic Object 타이핑
const Heading = (props: { title: string }) => <h2>{props.title}</h2>
const Heading = ({ title }: { title: string }) => <h2>{title}</h2>

const Box = ({ children }: { children: React.ReactNode }) => <div>{children}</div>
const Box: React.FunctionComponent = ({ children }) => <div>{children}</div> const List: React.FunctionComponent<{
items: string[];
onClick?: (item: string) => void;
}> = ({ items, onClick }) => ( <ul> {items.map((item, index) => ( <li key={index} onClick={() => onClick?.(item)}>{item}</li> ))} </ul> ) function App() { const onListClick = useCallback((item: string) => { alert(item) }, []); return ( <div> <Heading title="Introduciton" /> <Box> Hello there! </Box> <List items={["one", "two", "three"]} /> </div> ) }

Props 타입 작성

type Props = {
  message: string;
  count: number;
  disabled: boolean;
  names: string[]; // 타입의 배열
  status: "waiting" | "success"; // 정확한 문자열 값을 지정
  optional?: OptionalType; // optional

/* PREFERRED object */
  obj3: {  // 프로퍼티가 존재하는 객체 표현
    id: string;
    title: string;
  };
/* COMMON object */
  objArr: { // 객체의 배열
    id: string;
    title: string;
  }[];
/* DICT object */
  dict1: {  // 동일한 유형의 프로퍼티 속성 수에 관계 없는 dict 객체
[key: string]: MyTypeHere;
};
dict2: Record<string, MyTypeHere>; // dict1와 동일
/* NOT COMMON object */ obj: object; // 프로퍼티를 사용하지 않는 객체 표현. obj2: {}; // `object`와 거의 비슷, `Object`와 정확히 같음. /* VERY COMMON function */ onClick: () => void; // 인자를 취하거나 반환하지 않는 function onChange: (id: number) => void; // 명명된 파라미터가 있는 function onClick(event: React.MouseEvent<HTMLButtonElement>): void; /* NOT RECOMMENDED function */ onSomething: Function; };

Pros로 컴퍼넌트를 전달하는 경우

interface Props {
children0: React.ReactNode; // 최고!
children1: JSX.Element; // 나쁨. 배열을 고려하지 않음. children2: JSX.Element | JSX.Element[]; // 나쁨. 문자열을 허용하지 않음. children3: React.ReactChildren; // 적절하지 않은 타입. 유틸리티. children4: React.ReactChild[]; // 배열을 고려하긴함. functionChildren: (name: string) => React.ReactNode; // 자식을 렌더링하는 함수 타입으로 추천! style?: React.CSSProperties; // 스타일 프로퍼티를 전달 onChange?: React.FormEventHandler<HTMLInputElement>; // form 이벤트. 제너릭 매개변수는 vent.target 타입 props: Props & React.ComponentPropsWithoutRef<"button">; // button의 모든 props을 가장하고 해당 ref를 명시적으로 전달하지 않음 props2: Props & React.ComponentPropsWithRef<MyButtonWithForwardRef>; // MyButtonForwardedRef의 모든 props를 가장하고 해당 ref를 명시적으로 전달 }

children

type Props = {
  children: React.ReactNode;
  children: React.ReactChild;
  children: JSX.Element;
};

type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;

namespace JSX {
  interface Element extends React.ReactElement<any, any> { }
}
  • React.ReactNode

    • 클래스형 컴포넌트 render()에서 반환(return) 타입
    • null 허용
  • React.ReactChild

    • 함수형 컴포넌트 반환(return) 타입
    • React.ReactChild는 ReactElement와 string, number를 상속(extends)받음.
    • null 허용하지 않음
  • JSX.Element(ReactElement)

    • React.createElement() 함수의 반환(return) 타입
    • null 허용하지 않음
  • 왜 클래스형 컴포넌트와 함수형 컴포넌트를 분리했을까?

    • 사실 이 둘을 혼용을 해서 사용해도 큰 문제는 생기지 않을 것
    • 가장 큰 차이점은 반환(return) 타입으로 null을 허용하는지 유무
JSX.Element(ReactElement) ⊂ ReactChild ⊂ ReactNode
  • ReactNode, ReactChild, ReactElement 사용시 주의사항
    • 모두 children으로 {}를 받을 수 없음. 에러발생 시점이 다르다!
    • ReactNode는 런타임에서 에러발생.
    • ReactChild, JSX.Element(ReactElement)는 컴파일타임에서 에러발생

이벤트 타입

function App() {
  const onChangeFormHandler = (e: React.ChangeEvent<HTMLInputElement>) => {}
  const onKeyboardHandler = (e: React.KeyboardEvent<HTMLInputElement>) => {}
  const onClickHandler = (e: React.MouseEvent<HTMLButtonElement>) => {}
  const onSubmitHandler = (e: React.FormEvent<HTMLFormElement>) => {}
  return (
    <>
      <form onSubmit={onSubmitHandler}>
                <input onChange={onChangeFormHandler} />
                <input onKeyDown={onKeyboardHandler} />
                <button onClick={onClickHandler} />
            </form>
    </>
  );
}

export default App;

Detailed HTML props

  • ?? 연산자는 왼쪽 피연산자가 null 또는 undefined일 때만 오른쪽 피연산자를 반환
  • || 연산자는 왼쪽 피연산자가 falsy 값에 해당할 경우, 오른쪽 피연산자를 반환
const Button: React.FunctionComponent<
  React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement
    > & {
    title?: string; // 새로 추가한 커스텀 props
  }
> = ({ title, children, style, ...rest }) => (
<button {...rest} style={{ ...style, backgroundColor: "red", color: "white", fontSize: "xx-large", }} >
{title ?? children}
</button> );

Generic 컴퍼넌트

function UL<T>({
  items,
  render,
  itemClick,
}: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>,HTMLUListElement>
& { items: T[]; render: (item: T) => React.ReactNode; itemClick: (item: T) => void; }) { return ( <ul> {items.map((item, index) => ( <li onClick={() => itemClick(item)} key={index}> {render(item)} </li> ))} </ul> ); } function UL<T>({ items, render,
children,
}: React.PropsWithChildren<React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>,HTMLUListElement>
& { items: T[]; render: (item: T) => React.ReactNode;
}>) {
return ( <ul> {items.map((item, index) => ( <li key={index}> {render(item)} </li> ))} </ul> ); } function App() { return <> <UL items={todos} itemClick={(item) => alert(item.id)} render={(todo) => ( <> {todo.text} <button onClick={() => removeTodo(todo.id)}>Remove</button> </> )} /> </> }

참조링크