コンポーネントのpropsの型は結局どれを使えばいいのかいつも迷うし、公式っぽいドキュメントも見当たらないので、@types/reactのコードを読むことにした。
TL;DR

- HTML要素や関数コンポーネント、クラスコンポーネントのpropsをあらわす最も汎用的な型は
ComponentProps<T>型なので、基本的にはこれを使えばいいはず。 ComponentPropsWithoutRef<T>型は名前のとおりComponentProps<T>から"ref"を除いた型で、"ref"をpropsにもたないHTML要素をラップしたコンポーネントのpropsなんかに使うとよさそう。
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]
: {};
- 型パラメータ
TはJSX.IntrinsicElementsのキーまたはJSXElementConstructor<any>を拡張する必要がある。JSX.IntrinsicElementsについては詳しくは後述するけど簡単に言うとHTML要素ごとのpropsの型を持つオブジェクトのことで、これのキーというのはつまりHTML要素のことを指している。JSXElementConstructor<any>は関数コンポーネントまたはクラスコンポーネントのコンストラクタを表している。つまり、TはHTML要素か、関数またはクラスで定義されたコンポーネントのいずれかということになる。 - 右辺の
T extends JSXElementConstructor<infer Props> ? Props : ...の部分は、Tが関数またはクラスで定義されたコンポーネントだった場合はComponentProps<T>はそのpropsの型を表す、ということになる。こういう三項演算子みたいな書き方は条件付き型(Conditional Types)と呼ばれるようだ。infer演算子を型パラメータにつけると?以降でその型パラメータを参照できるようだ。 Tが関数またはクラスコンポーネントでない場合、JSX.IntrinsicElementsのキー、つまりHTML要素であればその要素のpropsの型がComponentProps<T>の型ということになる。
まとめると、 ComponentProps<T> は ComponentProps<"a"> や ComponentProps<MyComponent> のように使い、その型パラメータのコンポーネントのpropsを表す型ということになる。
ComponentPropsWithoutRef<T>
type ComponentPropsWithoutRef<T extends ElementType> = PropsWithoutRef<ComponentProps<T>>;
- 型パラメータ
TはElementTypeを拡張している。ElementTypeについては深追いしていないけどコメントを読むかぎり、propsを受け取れるコンポーネント全般を表す型のようだ。 PropsWithoutRef<T>に渡しているComponentProps<T>は上で見たとおり。
PropsWithoutRef<T>
type PropsWithoutRef<Props> =
Props extends any ? ("ref" extends keyof Props ? Omit<Props, "ref"> : Props) : Props;
Props extends any ? ... : ...は一見すると常に真じゃないの?と思うのだけど、この書き方は分配的条件付き型(Distributive Conditional Types)と呼ばれるもので、ここではPropsがA | Bのようなユニオン型だった場合にAとBそれぞれに対して評価した型をユニオンとして結合する際に使う。何言っているのかわからないかもしれない。具体的には今回のようなケースではOmit<A | B, "ref">ではなくOmit<A, "ref"> | Omit<B, "ref">という型にしたいときに使える書き方ということだ。Propsのキーに"ref"が含まれていれば"ref"をプロパティから除いた型をあらわす。Propsがユニオン型であってもそれぞれの型から"ref"を除いた型を結合したユニオン型をあらわすということになる。
まとめると、 ComponentPropsWithoutRef<T> は名前の通り T のpropsから "ref" を除いた型をあらわす。
JSX.IntrinsicElements
namespace JSX {
interface IntrinsicElements {
a: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
abbr: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
// ...
}
}
JSX.IntrinsicElementsはinterfaceで、プロパティにHTML要素が列挙されている。各プロパティの型はHTML要素ごとのpropsの型が宣言されている。
DetailedHTMLProps<E, T>
type DetailedHTMLProps<E extends HTMLAttributes<T>, T> = ClassAttributes<T> & E;
HTMLAttributes<T>を拡張した型パラメータEとClassAttributes<T>を結合したインターセクション型をあらわす。HTMLAttributes<T>については後述するけど、簡単に言うとHTML要素に共通して適用できる標準的な属性を指すっぽい。ClassAttributes<T>はkeyをプロパティとしてもつAttributesを拡張してrefを追加したRefAttributes<T>と実質的に同じ。つまりkeyとrefのこと。
AnchorHTMLAttributes<T>
interface AnchorHTMLAttributes<T> extends HTMLAttributes<T> {
download?: any;
href?: string | undefined;
hrefLang?: string | undefined;
media?: string | undefined;
ping?: string | undefined;
target?: HTMLAttributeAnchorTarget | undefined;
type?: string | undefined;
referrerPolicy?: HTMLAttributeReferrerPolicy | undefined;
}
HTMLAttributes<T>を拡張し、そこにhrefなど<a>タグで適用できる属性を加えたinterfaceをあらわしている。
HTMLAttributes<T>
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;
// Standard HTML Attributes
accessKey?: string | undefined;
autoCapitalize?: "off" | "none" | "on" | "sentences" | "words" | "characters" | undefined | (string & {});
autoFocus?: boolean | undefined;
className?: string | undefined;
// ...
}
AriaAttributesとDOMAttributes<T>を拡張し、HTMLで共通して適用できるグローバル属性を加えたinterfaceをあらわしている。AriaAttributesは名前のとおりaria-*属性をもつ。DOMAttributes<T>はchildrenや各種イベントハンドラーを持つinterfaceをあらわしている。
まとめると、 JSX.IntrinsicElements は各HTML要素のpropsをまとめたinterfaceになっており、propsの型は DetailedHTMLProps<E, T> であらわされる。例えば <a> タグのpropsである DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement> 型はHTML要素に共通した属性と <a> タグ固有の属性と ref および key を含む属性をあらわしている。