React + Typescript その2

前回のあらすじ

前回、ReactとTypescriptの環境を用意してReactを少しだけさわりました。

今回はTODOアプリのようなものを作りながら、ReactをTypescriptで使う様子をもう少し具体的に説明します。

雛形

まず、前回作った環境のsrcフォルダの中身をすべて削除してください。
次に、この ZIPファイル をダウンロードして、srcフォルダに展開してください。

展開されるファイルは以下の5つです。

ファイル 説明
index.tsx エントリポイント。Todoコンポーネントを呼ぶ。
index.css CSSファイル
Todo.tsx Todoコンポーネント。以下のコンポーネントをまとめる。
TodoEdit.tsx 入力フォーム
TodoList.tsx TODOリスト
TodoItem.tsx TODOリストの行

ブラウザで確認するとこんな感じです。CSSをほとんどあててないのでそっけないですが、デザインはこの記事に関係ないのでこのままで行きます。

テンプレート

各ソースコードの内容については詳しく説明しませんが、見てもらえばコンポーネントが以下のような構造に配置されていることがわかると思います。

Todo
 ┣ TodoEdit
 ┗ TodoList
   ┣ TodoItem
   ┣ TodoItem
   ┣ TodoItem
   ┣ TodoItem
   ┗ TodoItem

クリックイベント

手始めに、登録ボタンを押したときに「タイトル」と「本文」を取得するところまでを実装します。ちなみに、今回修正するソースコードは TodoEdit.tsx だけです。他は変更しません。

まず、TodoEditコンポーネントに登録ボタンを押したときに呼ぶ以下のメソッドを追加します。

TodoEdit.tsx

次に、登録ボタンが押されたときにonClick_Submit()メソッドを呼ぶようにします。

TodoEdit.tsx

クリックイベントの処理はこれだけです。特に説明もいらないと思います。

メソッドの呼び方が() => this.onClick_Submit()となっていますが、単にthis.onClick_Submit()でもメソッドは呼べます。ただ、それだと困ることがあるのですが、話が長くなるのでこの件は後ほど詳しく説明します。

Props と State

次に、フォームの値を取得できるようにするわけですが、これが意外に面倒です。
まず、ReactコンポーネントのPropsとStateについて説明します。

ReactのコンポーネントにはPropsとStateという2種類のデータを格納するためのオブジェクトがあります。
どちらもコンポーネントのデータを扱うためのオブジェクトです。それぞれの違いについては公式では以下のように説明されています。

state と props の違いは何ですか?

props(“properties” を短くしたもの)と state は、両方ともプレーンな JavaScript のオブジェクトです。どちらもレンダー結果に影響を及ぼす情報を持ってはいますが、ある重要な一点が異なっています。つまり、props は(関数引数のように)コンポーネントへ渡されるのに対し、state は(関数内で宣言された変数のように)コンポーネントの内部で制御されます。
https://ja.reactjs.org/docs/faq-state.html

使ってみて重要だなと思ったのは「Propsは変更不可、Stateは変更可」と「Stateを変更するとコンポーネントが再描画される」の2点です。まとめるとこんな感じでしょうか。

  • props
    • コンポーネントの外部から与えられるプロパティ値
    • コンポーネント自身には変更できない(参照のみ)
  • state
    • コンポーネントが内部で作成・使用する値
    • コンポーネント自身にも変更できる
    • 変更するとコンポーネントが再描画される

インタフェース

ではさっそく使ってみよう、といきたいところですが、ここでようやくTypescriptの話になります。

Typescriptでは変数にしろ関数の引数にしろ、型を正しく指定しなければコンパイルエラーになります。オブジェクトも同様にあらかじめ定義をする必要があります。

PropsとStateも、Javascriptであれば定義など不要なのですが、Typescriptではそれぞれのオブジェクトのインタフェースを定義する必要があります。

というわけで、TodoEditコンポーネントの定義を以下のように変更してください。

TodoEdit.tsx

タイトルと本文を格納するためにStateに変数titlecontentを用意しています。変数に型(string)が指定されているのもTypescriptの仕様です。

Propsは今回使わないので空のオブジェクトとして定義されています。

定義されたインタフェースがクラス宣言で指定されることで、TodoEditコンポーネントのPropsのインタフェースとしてTodoEditPropsInterfaceが、StateのインタフェースとしてTodoEditStateInterfaceが適用されます。

コンストラクタ

Stateを利用するには、まずコンストラクタで初期化する必要があります。

Propsはコンストラクタの引数として与えられます。また、コンストラクタでは必ずsuper()メソッドで親クラスのコンストラクタを呼ぶ必要があります(エラーになるので呼び忘れることはないですが)。

コンストラクタの引数propsの型としてTodoEditPropsInterfaceが指定されていることにも注目してください。これもTypescriptの仕様です。

TodoEdit.tsx

フォーム

次に、フォームが更新されるたびにその値をStateに保存します。
…ひどく面倒くさく感じるのですが、公式 にもこう書いてあるので従います。

ここまでくるとやることは簡単で、タイトルと本文が変更されたときに呼ばれるメソッドを用意して、

TodoEdit.tsx

onChangeから呼ぶようにします。

TodoEdit.tsx

メソッドの引数の型がanyとなっています。型を無視するものなので本来は使うべきじゃないのですが、面倒なので今回は使います。

動作確認

あとは確認用にクリックイベントを修正します。

TodoEdit.tsx

これでフォームに入力して「登録」ボタンを押すとアラートに入力値が表示されるはずです。

結果

アロー関数

最後に前述の() => this.onClick_Submit()について説明します。

これは アロー関数 と呼ばれるもので、ReactもTypescriptも関係なくて、Javascriptの話です。

詳しい話はリンク先に任せるとして、試しに() => this.onClick_Submit()this.onClick_Submitにしてみます。

TodoEdit.tsx

そして、フォームに値を入力してから登録ボタンを押すと、こんな感じでエラーになります。

エラー

「thisにはstateなんて無い!」と言われるわけですが、要するに「ここで使われているthisが、TodoEditクラスのインスタンスを指していない」ということです。Javascriptでよくあるあれです。

アロー関数を使うことでこれを解決できます。


実はもうひとつ、bindというものを使う方法もあります。
具体的には、コンストラクタに以下の一文を加えます。

公式 の方ではこちらが使われていますが、アロー関数の方が楽な気がするので、今回はそうしました。

次回に続く

ここまでの ソースコード です。

…フォームの値を取ってくるだけでずいぶんと長話になってしまいました。
いったんここで終了して次回に続きます。

次は、登録した内容を配列に保存して、TODOリストに表示するところまで進めるつもりです。