💡

ReactでComponentのPropsの型をどうするのがいいのか

2022/03/27に公開

初学者のReactのコンポーネント作成の課題

Reactを使うにあたってTypeScriptが普通になりつつある昨今ですが,ButtonやInputなどの粒度の大きいコンポーネントを作成するときにこんな感じにPropsが膨らんでしまっていることはありませんか?

import { ChangeEvent, VFC } from 'react';

type Props = {
  className?: string;
  id?: string;
  name: string;
  type?: string;
  placeholder?: string;
  defaultValue?: string;
  value: string;
  disabled?: boolean;
  onChangeText: (
    e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
  ) => void;
}

export const Input: VFC<Props> = ({
  className,
  id,
  name,
  type,
  placeholder,
  defaultValue,
  value,
  disabled,
  onChangeText,
}) => (
  <input
    className={className}
    id={id}
    name={name}
    type={type}
    placeholder={placeholder}
    defaultValue={defaultValue}
    value={value}
    disabled={disabled}
    onChange={(e): void => {
      onChangeText(e);
    }}
  />
);

🙉🙉🙉🙉🙉🙉🙉🙉🙉🙉🙉🙉🙉🙉

そんなときに,ちょっとReact,TypeScriptの中級者のとても簡単に使える知識を紹介します.

このようにPropsが多くなってしまう原因は,必要となるであろうHTML要素の属性の型を一つ一つ定義していることにあります.

例えばButtonコンポーネントの場合,まず,label(または,children)とonClickが必要なので追加し,そういえばdisableも必要なので足して,あとtypeもいるかーっと言った具合です.更にそこに属性以外のPropsも加わることでしょう.

解決法:各HTMLElementの属性の型があるので,それを使う

JSX.IntrinsicElementsもしくは[要素名]HTMLAttributesを使うことで,Propsに属性を型定義する必要がなくなります.

例えば,先程のInputコンポーネントはこのように書くことができます.

import { VFC } from 'react';

type Props = JSX.IntrinsicElements['input'] & {
  className?: string;
}

export const Input: VFC<Props> = ({ className, ...rest }) => {
  return <input className={className} {...rest} />;
};

😇😇😇😇😇😇😇😇😇😇😇😇😇😇😇

幸せになれましたでしょうか?

また,JSX.IntrinsicElements['input']InputHTMLAttributes<HTMLInputElement>に代えても問題ありませんので,そこらへんはお好みで.

私が使っているButtonコンポーネントの例も貼っておきます.(TailWindCSSを使っている点はご容赦ください)

import clsx from 'clsx';
import { VFC } from 'react';

type Props = JSX.IntrinsicElements['button'] & {
  className?: string;
  label: string;
  outline?: boolean;
  rounded?: boolean;
};

export const Button: VFC<Props> = ({
  className,
  label,
  outline,
  rounded,
  ...rest
}) => {
  const defaultClassName = 'text-white bg-primary2';
  const outlineClassName = 'text-primary2';

  return (
    <button
      className={clsx(
        className,
        'cursor-pointer rounded border-2 border-primary2 py-1 px-4 font-bold',
        outline ? outlineClassName : defaultClassName,
        rounded && 'rounded-full',
      )}
      {...rest}
    >
      {label}
    </button>
  );
};

参考:
https://kk-web.link/blog/20201023

Discussion