TR Output

フロントエンドエンジニアの備忘録

【React × TypeScript】HTML属性の型情報のインポート方法

ReactとTypeScriptでカスタムコンポーネントを作っていた際に、通常のHTMLタグの属性の型をどのように持ってくればいいんだろうと思ったので、調べたことをまとめました。

結論:React.ComponentPropsWithoutRef

※もしくは React.ComponentPropsWithRef

以下ように書くことができます。

import React from 'react'
type ButtonProps = React.ComponentPropsWithoutRef<'button'>

他の方法として、

  • ComponentProps
  • IntrinsicElements
  • [Element]HTMLAttributes
  • HTMLProps
  • HTMLAttributes

を使った実装があるみたいですが、なぜこれらではなく ComponentPropsWithoutRef を使うのか、という理由が下記で語られています。

react-typescript-cheatsheet.netlify.app

以下は上記の意訳になります。

ComponentProps

ComponentPropsWithRef の代わりに ComponentProps を使うこともできるが、
コンポーネントの参照(ref) が転送されるかどうかを明示的にすることをおすすめします。

JSX.IntrinsicElementsReact.[Element]HTMLAttributes

他に少なくとも2つの同等の方法がありますが、より冗長です。

// 方法 1: JSX.IntrinsicElements
type btnType = JSX.IntrinsicElements["button"]; // インライン化できない or エラーになる
export interface ButtonProps extends btnType {} // etc

// 方法 2: React.[Element]HTMLAttributes
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>

ComponentPropsの実装 を見ると、これは巧妙なラッパーになっていることが分かります。一方、2番目の方法はよく分からない命名/大文字の特殊なインターフェースに依存しています。

最終的には、TS特有の専門用語が少なく、使い勝手の良いComponentPropsの方法を選びました。しかし、お望みであればどちらの方法でも問題ないでしょう。

React.HTMLPropsReact.HTMLAttributes は絶対によくありません。

React.HTMLProps を使用するとこうなります。

export interface ButtonProps extends React.HTMLProps<HTMLButtonElement> {
  specialProp: string;
}
export function Button(props: ButtonProps) {
  const { specialProp, ...rest } = props;
  // エラー: 'string' 型 は '"button" | "submit" | "reset" | undefined' 型に割り当てることができません。
  return <button {...rest} />;
}

中で AllHTMLAttributes を使用しているため 広すぎる文字列型を推論してしまうのです。

React.HTMLAttributes を使用すると、このようになります。

export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
  /* etc */
}
// usage
function App() {
  // プロパティ 'type' が 'IntrinsicAttributes & ButtonProps' に存在しません。
  return <Button type="submit"> text </Button>;
}