どうも、ぽっぽです。
今回は、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
により呼び出しています。