どうも、ぽっぽです。
今回は、Reactのドキュメントで紹介されているサンプルコードのTodoアプリを説明します。
GitHub – microsoft\frontend-bootcamp: Frontend Workshop from HTML\CSS\JS to TypeScript\React\Reduxを参考にしています。
ソース
Release onlyReact · hatpoppo/todoApp-typescript-create-react-app · GitHubにTodoアプリのソースを公開しています。この記事ではそのソースを見ながら読んで欲しいです。ぜひソースをダウンロードして中身を確認してください。
このソースはCreate React App · Set up a modern web app by running one command.を使っています。複数のツールを組み合わせて必要な設定なども行ってくれているので便利でした。
npm install
npm start
Todoアプリを動作させる場合、NodeJSのコマンドプロンプトを開いて、ソースをダウンロードして展開した場所(package.jsonファイルがある)に移動します。1行目のコマンドを実行します。これはpackage.jsonに記載しているパッケージをインストールします、インストールが終わるまで数十秒ほど待ちます。
2行目のコマンドを実行するとブラウザにTodoアプリが表示されます。
終わるときは、コマンドプロンプト上でCtrl + Cをタイプします。
Componentとファイルの管理
最初のステップはアプリケーションをComponentに分解することです。
- TodoHeader
- TodoList
- TodoListItem
- TodoFooter
Componentは、ファイルごとに管理できます。それぞれモジュールとして管理され、importとexportを使います。
Componentを定義してexportキーワードを付与します。
export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState> {
...
利用するときは、importキーワードを使います。
import { TodoHeader } from "./components/TodoHeader";
TypeScript
ダウンロードしたソースはTypeScriptで記述しています。JavaScriptでは書く場合は拡張子がjsxでした。ソースではtsxになっています。
抽象化
複雑なComponentの繰り返しを表示する場合、通常は新しいComponentを作成する必要があります。
Todoアプリの場合TodoListItemです。
Index Signatures
TodoはIndex Signaturesと呼ばれるObjectで管理されています。
ひとまず、簡単にObjectについて立ち返ります。
let o = {
'name': 'Mike',
'blood': 'A',
'height': '170'
};
console.log(o.name); //"Mike"
console.log(o['name']); //"Mike"
オブジェクトにアクセスするには上記のように、2通りあります。よく使うのは.とkeyでのアクセスでしょう。
Index Signaturesは、このkeyを利用します。今回のTodoアプリの場合、keyを追番で追加していくイメージです。keyをかぶらないようにTodoをObjectに追加しています。
let o = {
'1': 'apple',
'2': 'arange'
}
実際にTodoListのソースを見ると
const filterdTodos = Object.keys(todos).filter(id => {
return filter === "all" || (filter === "completed" && todos[id].completed) || (filter === "active" && !todos[id].completed);
});
Object.keysというのは、引数のObjectのkeyを配列に格納します。 Object.keys(o)の場合で例えると、['1', '2']が返ってきます。
filterは配列のメソッドです。引数として与えられたテスト関数を各配列要素に対して実行し、それに合格したすべての配列要素からなる新しい配列を生成します。(参照:Array.prototype.filter() – JavaScript | MDN)
テスト関数では、新しいTodoを生成しています。注目はtodos[id].completedです。すべてのkeyに対してObjectを操作しています。
Stateについて
ReactではComponentの階層を上から下の1方向だけデータの流れがあります。Stateはそれを定義したComponentでだけ変更出来ます。下のComponentから変更する場合は、イベントハンドラーを下のComponentに渡します。
具体的な箇所の一例としてTodoAppでは、TodoHeaderを呼んでいます。PropsでaddTodoに_addTodo_のイベントハンドラーを指定しています。filterにfilterのStateを指定しています。
controlled とuncontrolled
<input>や<checkbox>のようなform要素はcontrolled とuncontrolledが存在します。Todoアプリではcontrolledで実装しています。
TodoHeaderではTodoを追加する<input>において、入力値をStateに保存して、onChangeメソッドで入力が変更されるごとにStateを更新しています。
TodoListItemでは<checkbox>でタスクのcompleteをonChangeメソッドでTodoAppのメソッドをコールバック関数で更新しています。
controlled とuncontrolledの違いを理解することは不要なバグなどの発生をなくすため重要です。詳しくはForms – Reactを参照してください。
静的型付け
JavaScriptは型を動的に解決します。アプリケーションが複雑になると、型が明示されていないとComponentを実装する上で不便になってきます。どんな型でPropsやStateを扱うかをTypeScriptを使って明示していくことが出来ます。
TodoListを見てみると、filter、todos、completeの3つのPropsを指定しています。
TodoListが受け取るPropsをinterfaceを使って定義します。
interface TodoListProps {
todos: Todos;
filter: FileTypes;
complete: (id: string) => void;
}
export class TodoList extends React.Component<TodoListProps, any> {
...
React.Componentを継承するときに、第一引数にPrps、第二引数にStateの型を指定します。
this.propsをdestructuringしている箇所では、interfaseで定義していない値を記載するとエラーになります。
また、TodoAppに戻って、TodoListのPropsにinterfaceで定義していない値を設定すると、エラーになります。
このように、静的型付けをすることでプログラミング時にエラーを発見しやすくなります。
型の共有
アプリの型を複数のComponentで利用できるように、TodoApp.types.tsのようにすることが出来ます。必要なComponentでimportにより呼び出しています。