6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

React+TypeScript+VS Codeでイベント処理関数に適切な型を「調査」して「定義」する方法

Posted at

イベント処理の型を覚えるのは大変

フォームやインプットなどのイベントハンドラに渡すイベント処理関数に対する型は、

FormEventHandler<HTMLFormElement>
ChangeEventHandler<HTMLInputElement>

といった型のエイリアスを呼びだして定義するか、

(event: FormEvent<HTMLFormElement>) => void
(event: ChangeEvent<HTMLInputElement>) => void

のように型のエイリアスと組み合わせて定義します。

このほかにもエイリアスが存在するため、全てを覚えるのは大変です。
仮に一字一句覚えたとしても誤って不適切な型を定義するとエラーの元になります。

本記事では、VS Codeの機能を活用して適切な型の調査方法定義方法について紹介します。
また応用編として、Material-UIの定義方法について紹介します。

前提

  • VS Codeは2021/9/15時点の最新版
  • Reactプロジェクトのモジュール構成は次の通り(Node.jsはv15.9.0)
package.json(一部抜粋)
{
  "dependencies": {
    "@emotion/react": "^11.4.1",
    "@emotion/styled": "^11.3.0",
    "@mui/icons-material": "^5.0.0-rc.0",
    "@mui/material": "^5.0.0-rc.0",
    "@mui/styled-engine": "^5.0.0-rc.0",
    "@testing-library/jest-dom": "^5.14.1",
    "@testing-library/react": "^12.1.0",
    "@testing-library/user-event": "^13.2.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-scripts": "4.0.3"
  },
  "devDependencies": {
    "@types/jest": "^27.0.1",
    "@types/node": "^16.7.10",
    "@types/react": "^17.0.19",
    "@types/react-dom": "^17.0.9",
    "@types/react-router-dom": "^5.1.8",
    "typescript": "^4.4.3"
  },
}
  • tsconfig.json"strict": trueを設定して、厳格に型をチェックしています。

そもそも型を定義しないと何が起こるのか?

簡単なフォームとイベント処理関数を型定義しないで実装したとします。

App.tsx
import { memo, VFC } from 'react';

const App: VFC = memo(() => {
  // formタグのイベント処理
  const handleOnSubmit = (event) => {
    console.log(event);
  };

  // inputタグのイベント処理
  const handleOnChange = (event) => {
    console.log(event);
  };

  return (
    <form onSubmit={handleOnSubmit}>
      <input type="text" name="text" onChange={handleOnChange} />
      <input type="submit" value="submit" />
    </form>
  );
});

export default App;

このときのVS Codeでは、各イベント処理のeventに対して次のエラーが出ます。
(eventの上にマウスカーソルをホバーすると見ることができます。)
スクリーンショット 2021-09-16 13.46.36.png
また、yarn startで実行すると、VS Codeのコンソールと表示用のブラウザに次のエラーが表示されます。

/(中略)/App.tsx
TypeScript error in /(中略)/App.tsx(5,27):
Parameter 'event' implicitly has an 'any' type.  TS7006

    3 | const App: VFC = memo(() => {
    4 |   // formタグのイベント処理
  > 5 |   const handleOnSubmit = (event) => {
      |                           ^
    6 |     console.log(event);
    7 |   };
    8 |

これはeventに対して暗黙的にany型が定義されていることが原因です。
jsxで保存してyarn startで実行すると正常動きますが、TypeScriptではこのような暗黙的なany型の定義があると許してくれません。厳しい…

このエラーを回避するにはイベント処理関数に型を定義するか、次のようにイベントハンドラに直接コールバックを書く必要があります。

    <form onSubmit={(event) => {console.log(event);}}>

この状態でeventマウスカーソルをホバーすると、eventに対してFormEventHandler<HTMLFormElement>の型が自動的に定義されていることがわかります。便利!
スクリーンショット 2021-09-16 14.22.18.png
ただし、リファクタリングでコールバック関数の部分を抜き出して、イベント処理関数として定義しようとすると再びエラーが発生するため、イベント処理関数への型定義は避けて通れません。

ではどうするのか?

型の調査方法

実は、前章でヒントが書かれています。

それは

マウスカーソルをホバー

です!!

formタグのonSubmitとinputタグのonChangeにそれぞれマウスカーソルをホバーすると、VS Codeの機能として次のヒントが表示されます。

formonSubmit
スクリーンショット 2021-09-16 15.00.12.png
inputonChange
スクリーンショット 2021-09-16 15.00.41.png
ここで重要になるのが、onSubmitやonChangeに定義されている型です。
一部省略しますが、次のように型が定義されています。

(省略).onSubmit?: React.FormEventHandler<HTMLFormElement> | undefined
(省略).onChange?: React.ChangeEventHandler<HTMLInputElement> | undefined

React.FormEventHandleReact.ChangeEventHandlerは'react'モジュールで用意されている型のエイリアスです。
これは、冒頭に述べた型のエイリアスそのもです。

冒頭のエイリアス(再掲)
FormEventHandler<HTMLFormElement>
ChangeEventHandler<HTMLInputElement>

型の定義方法

これらの型のエイリアスを使ってイベント処理関数に対して型を定義することで、イベントハンドラに直接コールバックを書いたときのようにeventに対して適切な型が自動的に設定されるようになります。

まず、ホバーした際に表示されるヒントの上にマウスカーソルを移動して、エイリアスの部分をドラッグ&ドロップすると文字列が選択できます。
これをコピーして、イベント処理関数を定義している変数に対してエイリアスを貼り付けて型を定義します。これだけ。

作業の様子は次のようになります。
ezgif-6-00cbce31d197.gif
型を定義した瞬間にhandleOnSubmit関数の引数eventのエラー表示(赤波線)が消えます。
このeventマウスカーソルをホバーすると、自動的に型定義されていることが確認できます。
スクリーンショット 2021-09-16 16.35.33.png
これは、formタグのイベントハンドラにコールバックを設定したときと同じ型が定義されています。
スクリーンショット 2021-09-16 14.22.18.png

handleOnChangeも同様の手順で型を定義してコードを整理すると、次のようになります。

App.tsx
import { ChangeEventHandler, FormEventHandler, memo, VFC } from 'react';

const App: VFC = memo(() => {
  // formタグのイベント処理
  const handleOnSubmit: FormEventHandler<HTMLFormElement> = (event) => {
    console.log(event);
  };

  // inputタグのイベント処理
  const handleOnChange: ChangeEventHandler<HTMLInputElement> = (event) => {
    console.log(event);
  };

  return (
    <form onSubmit={handleOnSubmit}>
      <input type="text" name="text" onChange={handleOnChange} />
      <input type="submit" value="submit" />
    </form>
  );
});

export default App;

型のエイリアスFormEventHandlerChangeEventHandlerは、'react'モジュールに格納されているため、次のように呼び出して使うことができます。

import { ChangeEventHandler, FormEventHandler, memo, VFC } from 'react';

ここでFormEventHandlerChangeEventHandlerについて簡単に説明します。

FormEventHandlerChangeEventHandlerの正体

これらもマウスカーソルをホバーすることでヒントを見ることができます。
FormEventHandler
スクリーンショット 2021-09-16 17.22.18.png
ChangeEventHandler
スクリーンショット 2021-09-16 17.07.52.png
それぞれ右辺を抽出すると、次のようになります。

(event: FormEvent<T>) => void
(event: ChangeEvent<T>) => void

ここで<T>はジェネリクスなので、<HTMLFormElement><HTMLInputElement>がそれぞれ代入されて、次のようになります。

(event: FormEvent<HTMLFormElement>) => void
(event: ChangeEvent<HTMLInputElement>) => void

つまりFormEventHandlerChangeEventHandlerは戻り値無しの関数の型がエイリアスとして定義されていたわけです。

型定義の恩恵

ここまで、型の調査方法と定義方法について説明しました。
JavaScript( jsx )で書くよりも手間が掛かるため面倒ですが、型を定義することで手間に見合った恩恵を受けることができます。

それぞれのイベント処理に入れたconsole.log(event).を入力してみてください。
すると次のようにインテリセンスが働きます。これが一つ目の恩恵です。
スクリーンショット 2021-09-16 17.57.38.png
スクリーンショット 2021-09-16 17.56.39.png
JavaScript( jsx )で同じことをやろうとすると、上記の候補は一つも出ません。
出てくるの使ったことがある語句程度です。
スクリーンショット 2021-09-16 22.56.47.png

次に、それぞれのイベント処理関数に入れたconsole.log(event)console.log(event.target.value)に変更します。
するとhandleOnSubmit側のvalueにエラーが発生します。
valueマウスカーソルをホバーすると、エラーメッセージが次のように表示されます。
スクリーンショット 2021-09-16 23.02.08.png
これは、formのevent.targetにはそもそもvalueのプロパティが存在しないからです。
(formの場合、event.currentTargetからFormDataに投げるなどして加工する必要があります)
このエラー表示が二つ目の恩恵です。

応用編:Material-UIの定義方法

ここまではformやinputなどのhtmlタグに対しての説明でした。
Reactには様々なUIフレームワークが存在します。
これらを使う場合もしっかりと型を定義する必要があります。
本章では応用編としてUIフレームワークの一つ、Material-UIの定義方法を説明します。

ソースコード

最初と同様に簡単なインプットフォームと、イベント処理関数を定義したとします。

SignIn.tsx
import { memo, VFC } from 'react';
import { Box, Button, Container, createTheme, CssBaseline, TextField, ThemeProvider } from '@mui/material';

const theme = createTheme();

const SignIn: VFC = memo(() => {
  const handleOnSubmit = (event) => {
    console.log(event);
  };

  const handleOnChange = (event) => {
    console.log(event);
  };

  return (
    <ThemeProvider theme={theme}>
      <Container component="main" maxWidth="xs">
        <CssBaseline />
        <Box sx={{marginTop: 8, display: 'flex', flexDirection: 'column', alignItems: 'center'}} >
          <Box component="form" onSubmit={handleOnSubmit} noValidate sx={{ mt: 1 }}>
            <TextField margin="normal" required fullWidth id="user" label="ニックネーム" name="user" autoFocus onChange={handleOnChange} />
            <Button type="submit" fullWidth variant="contained" sx={{ mt: 3, mb: 2 }}>
              はじめる
            </Button>
          </Box>
        </Box>
      </Container>
    </ThemeProvider>
  );

});
export default SignIn;

この中で、フォームに関する記述は次のようになります。

フォームのみを抜粋
<Box component="form" onSubmit={handleOnSubmit} noValidate sx={{ mt: 1 }}>
  <TextField margin="normal" required fullWidth id="user" label="ニックネーム" name="user" autoFocus onChange={handleOnChange} />
  <Button type="submit" fullWidth variant="contained" sx={{ mt: 3, mb: 2 }}>
    はじめる
  </Button>
</Box>

型の調査

BoxのonSubmitとTextFieldのonChangeマウスカーソルをホバーすると、次のようになります。
BoxonSubmit
スクリーンショット 2021-09-17 0.17.42.png
TextFieldonChangeスクリーンショット 2021-09-17 0.33.52.png
BoxのonSubmitはコピーアンドペーストで何とかなりそうですが、TextFieldのonChangeは特にエイリアスで定義されているわけではなく、onChange:(event:any)=>voidとなっています。
これはonSubmitに対して3つの関数が定義されていることに起因しています。
command(windowsはctrl)を押しながらクリックすると、次のように展開されます。
スクリーンショット 2021-09-17 0.42.33.png
これはイベントハンドラに入れるモノによって決まります。
今回の場合、型が未定義のイベント処理関数を入れたことで、3つのうち1つの関数が確定して最終的に(event:any)=>voidが表示されています。
しかし、これではダメです。

では、どうするのか?

答えは、コールバックを入れるです。
コールバックを入れた状態でマウスカーソルをホバーすると、次のようになります。
スクリーンショット 2021-09-17 1.01.45.png

型を定義

これらの情報をもとに型を定義すると、次のようになります。

  const handleOnSubmit:FormEventHandler<HTMLFormElement> = (event) => {
    console.log(event);
  };

  const handleOnChange:(event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void = (event) => {
    console.log(event);
  };

まとめ

イベント処理関数に適切な型を「調査」して「定義」する方法と応用方法について説明しました。

まとめると、型調査はマウスカーソルをホバーで型のエイリアスまたは関数の型を表示して、この情報をもとに型を定義する流れとなります。
しかし、イベントハンドラに複数の関数が定義されている場合、型調査で(event:any)=>voidと表示されることがあります。
その場合は、イベントハンドラにコールバック関数を入れて変化を確認してみてください。

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?