ReactコンポーネントPropsの型定義はどうするのが正解?
修正
2025年8月28日 - 修正
HTMLAttributes
と〇〇HTMLAttributes
では、
children
型は入っていない旨の内容を書いてしまいましたが、
そもそも両方ともchildren
型を持つDOMAttributes
型を継承しており、
children
型は今回紹介する例では全て使用できます。
interface DOMAttributes<T> {
children?: ReactNode | undefined;
~~~
}
interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {}
ButtonHTMLAttributes<T> extends HTMLAttributes<T>{}
その他でも、全体的に描き直しています。
検証が漏れており、申し訳ありませんでした🙇
はじめに
ReactでコンポーネントPropsの型定義には、以下の5つのどれかを使うことが多いです。
HTMLAttributes
-
〇〇HTMLAttributes
(要素限定) ComponentProps
ComponentPropsWithRef
ComponentPropsWithoutRef
例えば、これらを使わないと
以下のようにお馴染みのpropを毎回定義しないといけなくなります。
type Props = {
text: string;
className?: string; // お馴染みのやつ1
disabled?: boolean; // お馴染みのやつ2
onClick: ()=> void; // お馴染みのやつ3
}
const Button:React.FC<Props> = (props)=>{
// 以下省略 ~~~~~
}
ですが、人によって書き方がまちまちだったので、どれを採用するのがいいか調べてみました。
ソースをみよう
こちらにreactの型ファイルのソースがあるのでそれを紐解いていくことにします。
HTMLAttributes
vsButtonHTMLAttributes
(1).両者を見比べると、ButtonHTMLAttributes
はHTMLAttributes
を継承しています。
disabled
やtype
などButtonHTMLAttributes
にしかないため、該当するコンポーネントの〇〇HTMLAttributes
が用意されている場合はなるべくこちらを使った方が良さそうです。
interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
// React-specific Attributes
defaultChecked?: boolean | undefined;
defaultValue?: string | number | readonly string[] | undefined;
suppressContentEditableWarning?: boolean | undefined;
suppressHydrationWarning?: boolean | undefined;
// ~~~~ 以下省略
interface ButtonHTMLAttributes<T> extends HTMLAttributes<T> {
disabled?: boolean | undefined;
form?: string | undefined;
formAction?:
| string
| ((formData: FormData) => void | Promise<void>)
| DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_FORM_ACTIONS[
keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_FORM_ACTIONS
]
| undefined;
formEncType?: string | undefined;
formMethod?: string | undefined;
formNoValidate?: boolean | undefined;
formTarget?: string | undefined;
name?: string | undefined;
type?: "submit" | "reset" | "button" | undefined;
value?: string | readonly string[] | number | undefined;
}
ちなみに、HTMLAttributes<HTMLButtonElement>
の場合でも、
以下だとスプレッド構文の特性上通ります。余分な属性も通すためです。
export const Button = ({ children, ...props }: Props) => {
return <button {...props}>{children}</button>;
};
これだとちゃんとエラーになります。
この辺がややこしいんですよね。。
export const Button = ({ children, type, disabled, ...props }: Props) => {
return (
<button {...props} type={type} disabled={disabled}>
{children}
</button>
);
};
ComponentProps
vsComponentPropsWithRef
(2).ComponentProps
の箇所のコメントに書いてありました。
「たいていComponentProps
の代わりにComponentPropsWithRef
とComponentPropsWithoutRef
を使った方が良い。ref
を許可するかが明確になるから」とのこと。なので、ComponentPropsWithRef
を使いましょう。
/**
* Used to retrieve the props a component accepts. Can either be passed a string,
* indicating a DOM element (e.g. 'div', 'span', etc.) or the type of a React
* component.
*
* It's usually better to use {@link ComponentPropsWithRef} or {@link ComponentPropsWithoutRef}
* instead of this type, as they let you be explicit about whether or not to include
* the `ref` prop.
*
* @see {@link https://react-typescript-cheatsheet.netlify.app/docs/react-types/componentprops/ React TypeScript Cheatsheet}
*
* @example
*
* ```tsx
* // Retrieves the props an 'input' element accepts
* type InputProps = React.ComponentProps<'input'>;
* ```
*
* @example
*
* ```tsx
* const MyComponent = (props: { foo: number, bar: string }) => <div />;
*
* // Retrieves the props 'MyComponent' accepts
* type MyComponentProps = React.ComponentProps<typeof MyComponent>;
* ```
*/
type ComponentProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> = T extends
JSXElementConstructor<infer Props> ? Props
: T extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[T]
: {};
こちらの記事を参照、と書いてあるので見ると
以下のようである。
React 19+:
ComponentPropsWithRef<ElementType>が推奨
理由:関数コンポーネントでrefがpropsとして渡されるようになった
React ≤18:
refを転送する場合:ComponentPropsWithRef<ElementType>
refを転送しない場合:ComponentPropsWithoutRef<ElementType>
react 18の部分は、以下のような気がする。
React ≤18:
refを転送する(シンプル):ComponentPropsWithRef
refを転送する(forwardRef):ComponentPropsWithoutRef + forwardRef
refを転送しない:ComponentPropsWithoutRef
ちなみにComponentProps
はComponentPropsWithRef
のエイリアスらしい。
ComponentProps<ElementType> は、要素のすべての有効なプロパティまたはコンポーネントの推論されたプロパティを持つ型を構築します。これは ComponentPropsWithRef<ElementType> の別名です。
〇〇HTMLAttributes
vsComponentPropsWithoutRef
(3).この2つはほぼ同じなのでは??と継承元を辿ってみる
ButtonHTMLAttributes
ButtonHTMLAttributes -> HTMLAttributes
ComponentPropsWithoutRef
ComponentPropsWithoutRef -> ComponentProps -> JSX.IntrinsicElements -> DetailedHTMLProps
コードが難解すぎる。。
type ComponentPropsWithoutRef<T extends ElementType> = PropsWithoutRef<ComponentProps<T>>;
// ↓↓↓↓↓↓↓↓↓↓↓
type ComponentProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> = T extends
JSXElementConstructor<infer Props> ? Props
: T extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[T]
: {};
// ↓↓↓↓↓↓↓↓↓↓↓
interface IntrinsicElements {
// HTML
a: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>;
~~~
}
// ↓↓↓↓↓↓↓↓↓↓↓
type DetailedHTMLProps<E extends HTMLAttributes<T>, T> = ClassAttributes<T> & E;
HTMLAttributes
が出てきたので、同じになりそう。
自分の実力だと完全に読み切ることは出来ず、、、AIの力を借りた所、
どうやらこの2つは同じ型になるらしい。
AIを完全に信頼出来ないので、どなたかTS得意な方教えてください。
含まれる内容の比較表
型 | 基本属性 | 要素固有属性 | ref |
---|---|---|---|
❶.HTMLAttributes
|
✅ | ❌ | ❌ |
❷.ButtonHTMLAttributes
|
✅ | ✅ | ❌ |
❸.ComponentPropsWithoutRef(❷とほぼ同じ?)
|
✅ | ✅ | ❌ |
❹.ComponentPropsWithRef
|
✅ | ✅ | ✅ |
❺.ComponentProps(❹のエイリアス)
|
✅ | ✅ | ✅ |
実践的な使い分け
(1). 基本❹を使う
(2). refを禁止したい時は❸を使う。❷と❸は型が同じになるらしいが公式推奨の❸で統一で問題なさそう。
(3). ❹と❺は同じだが、refの許可を明確にするために❹を使う
(4). ❶は基本使わない
参考
宣伝
Next.jsを使ったフロントエンド開発などやってます。
お仕事のご依頼など、はこちらから!
Discussion