TypeScriptでReactのイベントにどう型指定するか

スタッフブログ

皆様どうも、こんにちは!
こまりのフロントエンドもやってるエンジニア、桑木です。

先日、TypeScriptでReactのイベントの型について考えさせられたので、そこで調べたことなんかを記事にしておきます。

前提知識

普通に型指定してみる

select要素を表示して、項目が選択されたらその値をconsoleに出力するだけのコンポーネントを作成します。

const SomethingComponent: React.FC = (props) => {
    const handleChange: React.ChangeEventHandler<HTMLSelectElement> = (ev) => {
        console.log(ev.target.value);
    };
    return (
        <div>
            <select onChange={handleChange}>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
            </select>
        </div>
    );
};

ここではイベントハンドラに型指定していますが、型推論によってevReact.ChangeEvent<HTMLSelectElement>になります。

const handleChange = (ev: React.ChangeEvent<HTMLSelectElement>) => {

こう書いても結果的に互換性のある型に推論されるので、イベントハンドラとして使用できます。

バブリングしたイベントをキャッチする

この例のコードではわざわざバブリングを利用する意味はありませんが、select要素の親のdiv要素でバブリングしてきたイベントをキャッチするとしたら次のようになります。

const SomethingComponent: React.FC = (props) => {
    const handleChange: React.ChangeEventHandler<HTMLDivElement> = (ev) => {
        if (!(ev.target instanceof HTMLSelectElement)) {
            return;
        }
        console.log(ev.target.value);
    };
    return (
        <div onChange={handleChange}>
            <select>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
            </select>
        </div>
    );
};

イベントハンドラのジェネリクスに指定している型がHTMLDivElementに変わっているのと、if文でev.targetHTMLSelectElementかどうか判別する処理が加わっています。

イベントハンドラのジェネリクスにはイベントハンドラを設定する要素を指定する

イベントハンドラのジェネリクスがHTMLSelectElementのままだと、divonChangeに割り当てることができない、というエラーが出ます。Reactの型定義を参照すればわかりますが、イベントハンドラのジェネリクスはcurrentTargetの型を指定することになっています。

イベントオブジェクトのtargetプロパティはイベントが発生した要素、currentTargetプロパティはイベントをキャッチした要素=イベントハンドラを設定した要素です。

つまり、イベントハンドラのジェネリクスにはonChange等でイベントハンドラを実際に設定する要素を指定する必要があるということです。

なぜtargetの型指定ができないのか

このコードでは、if文でev.targetHTMLSelectElement型以外の時は関数を抜けています。こうすることで、その後のev.targetHTMLSelectElement型として認識されます。これによってev.target.valueプロパティにアクセスすることができます。

ところで、こんな面倒なことをしなくてもジェネリクスなどでev.targetの型を指定できないのでしょうか。

結論から言うと、指定できません。実はこれもReactの型定義にその理由がコメントで書かれているのですが、currentTargetはイベントハンドラを設定した要素しかありえないのに対して、targetはその子孫に含まれるどの要素の可能性もあるので、コンパイル時にtargetの型を決定する意味があまりないからです。

changeイベントに関してだけ言うならtargetEventTargetとジェネリクスで指定した型の交差型になっているので、もとのバブリングしていない方のコードでは何の判別もなしにvalueプロパティにアクセスできています。changeイベントを発生させる要素には確実にvalueプロパティがあるのでこれで良いのでしょう。

そもそもcurrentTargetを使おう

実際問題として、バブリングの仕組みを積極的に使うことはそんなにありません。通常はイベントハンドラを設定した要素でイベントが発生することを期待します。

currentTargetを使うことでバブリングを巧みに利用したコードでないことが示せるので、なるべくcurrentTargetを使用するコードを記述した方が良いのかもしれません。