前回のあらすじ
前回、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
1 2 3 4 |
// クリック:登録 private onClick_Submit() { alert("OK"); } |
次に、登録ボタンが押されたときにonClick_Submit()
メソッドを呼ぶようにします。
TodoEdit.tsx
1 2 3 4 |
<div> <button>新規</button> <button onClick={() => this.onClick_Submit()}>登録</button> </div> |
クリックイベントの処理はこれだけです。特に説明もいらないと思います。
メソッドの呼び方が() => 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
1 2 3 4 5 6 7 8 9 10 11 |
// Propsインタフェース interface TodoEditPropsInterface { } // Stateインタフェース interface TodoEditStateInterface { title: string; content: string; } class TodoEdit extends React.Component<TodoEditPropsInterface, TodoEditStateInterface> { |
タイトルと本文を格納するためにStateに変数title
とcontent
を用意しています。変数に型(string
)が指定されているのもTypescriptの仕様です。
Propsは今回使わないので空のオブジェクトとして定義されています。
定義されたインタフェースがクラス宣言で指定されることで、TodoEditコンポーネントのPropsのインタフェースとしてTodoEditPropsInterface
が、StateのインタフェースとしてTodoEditStateInterface
が適用されます。
コンストラクタ
Stateを利用するには、まずコンストラクタで初期化する必要があります。
Propsはコンストラクタの引数として与えられます。また、コンストラクタでは必ずsuper()
メソッドで親クラスのコンストラクタを呼ぶ必要があります(エラーになるので呼び忘れることはないですが)。
コンストラクタの引数props
の型としてTodoEditPropsInterface
が指定されていることにも注目してください。これもTypescriptの仕様です。
TodoEdit.tsx
1 2 3 4 5 6 7 |
public constructor(props: TodoEditPropsInterface) { super(props); this.state = { title: '', content: '', }; } |
フォーム
次に、フォームが更新されるたびにその値をStateに保存します。
…ひどく面倒くさく感じるのですが、公式 にもこう書いてあるので従います。
ここまでくるとやることは簡単で、タイトルと本文が変更されたときに呼ばれるメソッドを用意して、
TodoEdit.tsx
1 2 3 4 5 6 7 8 9 |
// フォーム変更:タイトル private onChange_Title(event:any) { this.setState({title: event.target.value}); } // フォーム変更:本文 private onChange_Content(event:any) { this.setState({content: event.target.value}); } |
onChange
から呼ぶようにします。
TodoEdit.tsx
1 2 3 4 5 6 7 8 |
<div> <div>タイトル</div> <input type='text' value={this.state.title} onChange={(e) => this.onChange_Title(e)} /> </div> <div> <div>本文</div> <textarea value={this.state.content} onChange={(e) => this.onChange_Content(e)} /> </div> |
メソッドの引数の型がany
となっています。型を無視するものなので本来は使うべきじゃないのですが、面倒なので今回は使います。
動作確認
あとは確認用にクリックイベントを修正します。
TodoEdit.tsx
1 2 3 4 |
// クリック:登録 private onClick_Submit() { alert(this.state.title + " " + this.state.content); } |
これでフォームに入力して「登録」ボタンを押すとアラートに入力値が表示されるはずです。
アロー関数
最後に前述の() => this.onClick_Submit()
について説明します。
これは アロー関数 と呼ばれるもので、ReactもTypescriptも関係なくて、Javascriptの話です。
詳しい話はリンク先に任せるとして、試しに() => this.onClick_Submit()
をthis.onClick_Submit
にしてみます。
TodoEdit.tsx
1 2 3 4 |
<div> <button>新規</button> <button onClick={this.onClick_Submit}>登録</button> </div> |
そして、フォームに値を入力してから登録ボタンを押すと、こんな感じでエラーになります。
「thisにはstateなんて無い!」と言われるわけですが、要するに「ここで使われているthis
が、TodoEdit
クラスのインスタンスを指していない」ということです。Javascriptでよくあるあれです。
アロー関数を使うことでこれを解決できます。
実はもうひとつ、bind
というものを使う方法もあります。
具体的には、コンストラクタに以下の一文を加えます。
1 |
this.onClick_Submit = this.onClick_Submit.bind(this); |
公式 の方ではこちらが使われていますが、アロー関数の方が楽な気がするので、今回はそうしました。
次回に続く
ここまでの ソースコード です。
…フォームの値を取ってくるだけでずいぶんと長話になってしまいました。
いったんここで終了して次回に続きます。
次は、登録した内容を配列に保存して、TODOリストに表示するところまで進めるつもりです。