delhi09の勉強日記

技術トピック専用のブログです。自分用のメモ書きの投稿が多いです。あくまで「勉強日記」なので記事の内容は鵜呑みにしないでください。

【Reactの勉強】チュートリアルをやってみる(「フォームの操作とイベントハンドリング」)

概要

前回に引き続き、以下の日本大学文理学部情報科学科の教授の方がクリエイティブ・コモンズで公開してくださっているチュートリアルをやっていく。

zenn.dev

また、適宜、以下の本を参考にさせて頂きながら進める。(以下、『りあクト!』)

oukayuka.booth.pm

今回はデプロイを除けば最後のセクションである「フォームの操作とイベントハンドリング」をやった。

TypeScriptでやろうとすると型をちゃんと定義しないといけなくて結構大変だった。いろいろ調べながらやった。

バージョン

「Reactの勉強」で使用している主なソフトウェアのバージョンは以下の通り。

  • node: 15.5.0
  • react-create-app: 4.0.1
  • react: 17.0.1
  • react-dom: 17.0.1
  • typescript: 4.1.3
  • eslint: 7.17.0
  • prettier: 2.2.1
  • stylelint: 13.8.0

ポイント

以下、TypeScriptでやる場合にポイントだったところを書いていく。

「event」変数に型を定義する

元のコードには以下のような「handleSubmit」というsubmitイベントを受け取って処理をする関数がある。

function handleSubmit(event) {
// 省略
}

TypeScriptの場合は、まずはこの引数の「event」に型を定義する必要がある。

以下の記事を読んで、React.FormEvent<HTMLFormElement>を定義したら解決した。
qiita.com

※ 調べているときにジェネリクスに「HTMLInputElement」を渡すコードも出てきたが、今回のケースはformのsubmitイベントなので「HTMLFormElement」を渡すようである。

コードは以下

const handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
// 省略
}

「event.target 」に型を定義する

同じく「handleSubmit」に関して、元のコードでは以下のように1行で「event.target.elements」を変数「breed」に分割代入している。

function handleSubmit(event) {
  // 省略
  const { breed } = event.target.elements;
  // 省略
}

この部分をTypeScriptで書くと、「event.target」は「elements」という属性を持っていないということで、ESLintでエラーになる。

f:id:kamatimaru:20210213050340p:plain

これを解消するためには、以下のように「event.target」を型アサーションで「HTMLFormElement」と型定義した上で、いったん変数に保存する必要があった。(React.FormEventのジェネリクスに渡している型と同じ型になる。)

const eventTarget = event.target as HTMLFormElement;
const { breed } = eventTarget.elements;

このように書くことで、「eventTarget」は「elements」という属性を持っているということをTypeScriptが認識してくれるようになる。

少し文脈は違うが、以下の記事を参考にさせて頂いて解決した。
qiita.com

elementsからフォームの入力値を取り出すときに「namedItem」を使う

同じ部分に関して、元のコードでは以下のようにオブジェクトを分割代入している。

const { breed } = eventTarget.elements;

この部分も、TypeScriptでは「elements」は「breed」という属性を持っていないということで、ESLintでエラーになる。

f:id:kamatimaru:20210213052646p:plain

VS Codeによると「elements」の型は「HTMLFormControlsCollection」とのことである。

developer.mozilla.org

公式ドキュメントを読むと、「HTMLFormControlsCollection」は「namedItem」というメソッドを持っており、これでFormの各項目のnameの文字列を引数に指定すると、対象のElementを取得できるようである。

ただし、以下の2点を対応する必要がある。

  • 「namedItem」のreturnの型は「Element | RadioNodeList | null」になっているので、型アサーションで「HTMLInputElement」を定義する必要がある。
  • 「namedItem」は項目が「elements」に存在しない場合にnullを返すので、代入する変数はnullを許可する必要がある。

上記を全て対応すると以下のようなコードになった。

const breed: HTMLInputElement | null = eventTarget.elements.namedItem(
      'breed',
    ) as HTMLInputElement;
if (breed != null) {
   props.onFormSubmit(breed.value);
}

「handleSubmit」関数におけるポイントは以上。

Formコンポーネントのpropsに渡す関数に型を定義する

次はFormコンポーネントに関して、元のコードでは以下のようにFormコンポーネントを実装している。

function Form(props) {
// 省略
  return (
  // 省略
  );
}

このpropsには、以下のように「reloadImages」という関数が渡ってくる。

const Main: FC = () => {
  // 省略
  const reloadImages = (breed: string) => {
  // 省略
  };

  return (
    <main>
      <!-- 省略 -->
          <Form onFormSubmit={reloadImages} />
        <!-- 省略 -->
    </main>
  );
};

TypeScriptでは、この関数の型も定義しないと、以下のようにESLintでエラーになってしまう。

f:id:kamatimaru:20210213055421p:plain


以下の記事を参考にさせて頂いて、関数の型を作成してpropsに定義したところ、解消できた。
qiita.com

コードは以下

type ReloadImagesFunction = (param: string) => void;

interface FormProps {
  onFormSubmit: ReloadImagesFunction;
}

const Form: FC<FormProps> = (props) => {
  // 省略
};

ハマったポイントは以上

成果物

これで実装に関しては全てのセクションが完了した。
柴犬と秋田犬の画像を選べるようになった。

f:id:kamatimaru:20210213060730p:plain

f:id:kamatimaru:20210213060833p:plain

以上