結局 React のコンポーネントにはどうやって型をつければ良いのさ
React TypeScript Cheatsheet の Useful Patterns by Use Case の Wrapping/Mirroring に答えが書かれていました。
自分は function
でなく const
で作成することが多い(どっちが良いんですかね?)ので、以下のように書くのが良さそうです。
import React, { ComponentPropsWithoutRef, FC } from "react";
export type ButtonProps = ComponentPropsWithoutRef<"button"> & {
specialProp?: string;
};
const Button: FC<ButtonProps> = ({ specialProp, ...props }: ButtonProps) => {
console.log(specialProp);
return <button {...props} />;
};
export default Button;
確かに見通し良いですね、自分も今後はこう書いていこうかなーと思います。
蛇足
上の記事を読んでいくと、Why not ComponentProps or IntrinsicElements or [Element]HTMLAttributes or HTMLProps or HTMLAttributes?
って欄が出てきます。
ComponentProps はイマイチ
ComponentProps
は ref
が含まれているのか含まれていないのかわかりづらいですよね。
そのため ComponentProps
は使用せず、ComponentPropsWithoutRef
か ComponentPropsWithRef
を使うほうが良さそうです。
IntrinsicElements or [Element]HTMLAttributes はまぁ…
以下のように書いても正常動作します。
import React, { FC } from "react";
export type ButtonProps = JSX.IntrinsicElements["button"] & {
specialProp?: string;
};
import React, { ButtonHTMLAttributes, FC } from "react";
export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
specialProp?: string;
};
Looking at the source for ComponentProps shows that this is a clever wrapper for JSX.IntrinsicElements, whereas the second method relies on specialized interfaces with unfamiliar naming/capitalization.
ComponentProps のソースを見ると、これが JSX.IntrinsicElements の巧妙なラッパーであることがわかりますが、2 番目の方法は、なじみのない名前付け/大文字の使用を伴う特殊なインターフェイスに依存しています。
Ultimately, we picked the ComponentProps method as it involves the least TS specific jargon and has the most ease of use. But you'll be fine with either of these methods if you prefer.
最終的に、TS 固有の専門用語が最も少なく、使いやすさが最も高い ComponentProps メソッドを選択しました。 ただし、必要に応じて、これらの方法のいずれでも問題ありません。
とのことなので IntrinsicElements
でも [Element]HTMLAttributes
を使用しても問題はなさそうです。
ただ ComponentPropsWithoutRef
のラッパーとは書かれていないので、最も良い書き方ではないかもしれないです。
React.HTMLProps or React.HTMLAttributes はダメ
import React, { FC, HTMLProps } from "react";
export type ButtonProps = HTMLProps<HTMLButtonElement> & {
specialProp?: string;
};
const Button: FC<ButtonProps> = ({ specialProp, ...props }: ButtonProps) => {
console.log(specialProp);
// error: 型 'string' を型 '"button" | "submit" | "reset"' に割り当てることはできません。ts(2322)
return <button {...props} />;
};
export default Button;
これはわかりやすくダメですね。
HTMLProps
は AllHTMLAttributes
を継承しているので、特定のタグで使用すべきではありません。
import React, { FC, HTMLAttributes } from "react";
export type ButtonProps = HTMLAttributes<HTMLButtonElement> & {
specialProp?: string;
};
こっちは呼ぶ側でエラーが起きます、これも汎用的な型なんですかね?