やっと理解できたTypeScriptでのevent.target.valueの型定義

## はじめに
TypeScriptとReactで開発していて、よく悩むのがフォーム関連のイベントの型です。

今回は型の誤りにより`event.target.value`で値を取得できないという事態に遭遇したので、その理由と対応方法について解説します。

## 誤った処理の定義
ラジオボタンの値取得処理を以下のように定義していました。

```react
  const clickHandler = (event: MouseEvent<HTMLInputElement>) => {
    console.log(event.target.value);
  };
```
一見すると合っていそうですが、実際にはVS Code上では以下のようにエラーとなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/a9fd9f7c-b63f-448e-9c1d-43089539c5fc.png)

### 暫定対応
この実装をしていた当時はまったく理由がわからなかったので、代わりに以下のようにして`value`を取得しました。
```typescript
  const clickHandler = (event: MouseEvent<HTMLInputElement>) => {
    // console.log(event.target.value);
    console.log(event.currentTarget.value);
  };
```
一応解決はしましたが、`currentTarget`プロパティからは`value`が取得できるのに、`target`プロパティからは取得できないという不可解な事態にもやもやしていました。

## 正しい処理の定義
そもそも、ラジオボタンの値取得方法が誤っていました。
`onClick`に設定するためのクリックイベント取得処理を書いていましたが、本来は`onChange`に値変更時の処理を記載するべきです。
```typescript
  const changeHandler = (event: ChangeEvent<HTMLInputElement>) => {
    console.log(event.target.value);
  };
```

このようにすると、`target`プロパティから`value`を取得できます。
先ほどとどこが違うかわかるでしょうか。

誤った処理ではクリックイベントを取得する想定だったので、イベントの型が
`MouseEvent<HTMLInputElement>`
となっています。

一方、こちらの処理ではイベントの型が
`ChangeEvent<HTMLInputElement>`
となっています。

このように、設定する型によって同じプロパティでも取得の可否が分かれてきます。

## 何が違うのか
これで一件落着とおもいきや、実はひとつ引っかかることがあります。

実は`value`プロパティは、`HTMLInputElement`に属するプロパティです。

つまり、`MouseEvent<HTMLInputElement>`だろうが、`ChangeEvent<HTMLInputElement>`だろうが、ジェネリクスに`HTMLInputElement`を設定している以上、どちらからも`value`が取得できそうです。

### ChangeEventの型
Reactの型定義を確認します。
```typescript:ChangeEventの型定義
interface ChangeEvent<T = Element> extends SyntheticEvent<T> {
  target: EventTarget & T;
}
```
`ChangeEvent`の場合は`target`の型が`EventTarget`と`T`の交差型になっています。
よって、`ChangeEvent`は`EventTarget`と`T`の両方のプロパティを持ちます。

そして、`T`にはジェネリクスに設定した型（ここでは`HTMLInputElement`）が設定されます。
そのため、`target`プロパティは`value`をもつのです。

### MouseEventの型
こちらもReactの型定義から抜粋します。
```typescript:MouseEventの型定義
interface MouseEvent<T = Element, E = NativeMouseEvent> extends UIEvent<T, E> {
  altKey: boolean;
  button: number;
  buttons: number;
  clientX: number;
  clientY: number;
  ctrlKey: boolean;
  /**
   * See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method.
   */
  getModifierState(key: ModifierKey): boolean;
  metaKey: boolean;
  movementX: number;
  movementY: number;
  pageX: number;
  pageY: number;
  relatedTarget: EventTarget | null;
  screenX: number;
  screenY: number;
  shiftKey: boolean;
}
```

確認するとわかりますが、そもそも`target`プロパティがありません。
そうなると、そもそも`MouseEvent`からは`target`自体が取得できないはずです。

`MouseEvemt`は`UIEvent`を継承しているので、さらに追ってみます。
なお、ここでの`T`は先ほどの例においては`HTMLInputElement`が設定されます。

#### UIEvent
```typescript:UIEventの型定義
interface UIEvent<T = Element, E = NativeUIEvent> extends SyntheticEvent<T, E> {
  detail: number;
  view: AbstractView;
}
```
ここにも`target`はありません。
今回の例では`T`は`UIEvent`から引き継いで`HTMLInputElement`です。
また、`SyntheticEvent`を継承しているようなので、さらに深く追っていきます。

#### SyntheticEvent
```typescript:SyntheticEventの型定義
interface SyntheticEvent<T = Element, E = Event>
  extends BaseSyntheticEvent<E, EventTarget & T, EventTarget> {}
```
何も定義されていません。
`BaseSyntheticEvent`を継承しているので、もう一段深く確認します。
なお、`UIEvent`から引き継いだ`T`型は`BaseSyntheticEvent`の2番目のジェネリクスに渡されます。
3番目の型は`EventTarget`です。
※`EventTarget`は次のように、空の定義となっています。
```typescript
interface EventTarget {}
```

#### BaseSyntheticEvent
```typescript:BaseSyntheticEventの型定義
interface BaseSyntheticEvent<E = object, C = any, T = any> {
  nativeEvent: E;
  currentTarget: C;
  target: T;
  bubbles: boolean;
  cancelable: boolean;
  defaultPrevented: boolean;
  eventPhase: number;
  isTrusted: boolean;
  preventDefault(): void;
  isDefaultPrevented(): boolean;
  stopPropagation(): void;
  isPropagationStopped(): boolean;
  persist(): void;
  timeStamp: number;
  type: string;
}
```
ようやく`target`が出てきました。

`target`の型`T`はジェネリクスの3番目の型になっています。
3番目の型は`EventTarget`という空の定義でした。
つまり、`value`などのプロパティを持ちません。

ここまで長い旅路でしたが、`MouseEvent`の`target`はプロパティを持たないということがわかりました。

そのため`event.target.value`がエラーとなるのです。

一方、`currentTarget`は型`C`です。
これは`SyntheticEvent`においては`EventTarget`と`T`の交差型でした。
そして`SyntheticEvent`の`T`は`MouseEvent`での`T`と同じであるため、ここでは`HTMLInputElement`となります。

そのため、`event.currentTarget.value`で値が取得できた、ということです。

## まとめ
Reactの型定義を深堀りしたので、複雑になりましたが、ちょっとした型の違いで取得できたり、できなかったりする謎が解けました。

TypeScriptでの型は、わずらわしさを感じることも多いですが、適切に設定することで開発効率の向上につながるので、今後も疑問に思ったことは一つずつ紐解いていきます。