https://qiita.com/Takepepe/items/f1ba99a7ca7e66290f24

React x TypeScript の鬼門のひとつに「props に記述する EventCallback の適切な書き方が分からない」というものがあります。さて、このコンポーネントの type Props どう型定義するべきでしょうか?

const View: React.FC<Props> = props => (
  <form onSubmit={props.onSubmit}>
    <input
      type="text"
      onClick={props.onClick}
      onChange={props.onChange}
      onKeyPress={props.onkeypress}
      onBlur={props.onBlur}
      onFocus={props.onFocus}
    />
    <div onClick={props.onClickDiv} />
  </form>
)

「とくに困らないので〜」という理由でつぎの様に妥協していませんか? 部分的妥協は、TypeScript の良い点でもありますが、せっかくなのできちんとしたいですよね。

type Props = {
  onClick: (event: any) => void
  onChange: (event: any) => void
  onkeypress: (event: any) => void
  onBlur: (event: any) => void
  onFocus: (event: any) => void
  onSubmit: (event: any) => void
  onClickDiv: (event: any) => void
}

ただ、EventCallback の型はたくさんあるし、初見ではどれを指定すれば良いのか…迷いますよね。自分は普段、VSCode を使っているため、迷ったときは VSCode のコードヒントに頼ることにしています。つぎのキャプチャは、onClick まで空で書いて、 onClick にマウスオーバーした時のものです。

答えが全部、型推論に書いてありましたね!これで指定方法が分かったので、コピペします。

type Props = {
  onClick: (event: React.MouseEvent<HTMLInputElement>) => void
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
  onkeypress: (event: React.KeyboardEvent<HTMLInputElement>) => void
  onBlur: (event: React.FocusEvent<HTMLInputElement>) => void
  onFocus: (event: React.FocusEvent<HTMLInputElement>) => void
  onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
  onClickDiv: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
}

Generics に指定している型はなに?

さて、React.MouseEvent<XXX>React.ChangeEvent<XXX>の様に、Generics にHTMLInputElement型や HTMLDivElement型など、いろいろな型が指定されていますね…。

import もしていないし、この型はどこから来たもので、いったいこれらの指定で何が変わるのでしょうか? HTMLInputElement型の中身をみてみましょう。型にマウスを当て、Option キーを押しながらマウスクリックすることで、いつもの様に型定義へジャンプします。

@types/react/global.d.ts

interface HTMLDivElement extends HTMLElement { }
interface HTMLDListElement extends HTMLElement { }
interface HTMLEmbedElement extends HTMLElement { }
interface HTMLFieldSetElement extends HTMLElement { }
interface HTMLFormElement extends HTMLElement { }
interface HTMLHeadingElement extends HTMLElement { }
interface HTMLHeadElement extends HTMLElement { }
interface HTMLHRElement extends HTMLElement { }
interface HTMLHtmlElement extends HTMLElement { }
interface HTMLIFrameElement extends HTMLElement { }
interface HTMLImageElement extends HTMLElement { }
interface HTMLInputElement extends HTMLElement { }
interface HTMLModElement extends HTMLElement { }
interface HTMLLabelElement extends HTMLElement { }
interface HTMLLegendElement extends HTMLElement { }
interface HTMLLIElement extends HTMLElement { }

紙面の都合上省略していますが、こんなにあるのか…とたくさん出てきましたね!よくみてみると…。ほとんどが extends HTMLElement { } ですね。名前が違うだけで、全部一緒じゃないですかw でも実は、interface にはオーバーロードの仕組みがあり、それぞれの型は、@types/react/global.d.ts で明示されていない定義を上書きしています。元の HTMLInputElement型は一体どこからきているかというと…

キャプチャ右側のとおり一覧で表示されます。 lib.dom.d.tsglobal.d.ts で定義されていることが分かりますね。global.d.ts は、@types/react で提供されている定義、lib.dom.d.ts は TypeScript から提供されている定義です。

lib.dom なんて入れた覚えが無いよー」と不思議に思うかもしれませんが、これの出どころは、tsconfig の lib 指定です。tsconfig の lib 指定が無い場合、他の指定内容(targetなど)に応じて適切なデフォルト lib が import されるためこの様になります。出所不明だった HTMLInputElement型 はこの様に、interface のオーバーロードの仕組みで塗り重ねられていたんですね。

必要に応じて、抽象度を見極める