ここから本文です

SPA超入門 ReactとAngular2の使い方

@IT 4/12(水) 7:10配信

 フロントエンド開発のアーキテクチャである「SPA(Single Page Application)」について、開発に必要となる各種フレームワークの特徴や作り方の違いなどを紹介する本連載「モダンなフロントエンド開発者になるためのSPA超入門」。

 前回の「ゼロから学ぶ! Single Page Applicationの特徴と主なフレームワーク5選」ではSPAの特徴と取り巻く環境、フレームワークを紹介しました。今回は、前回紹介したフレームワークの中から、特に注目度が高い「React」と「Angular2」を使い、SPA開発のイメージをつかんでいただくとともに、それぞれのフレームワークの違いを明らかにしていきます。

 本連載で使用する環境は表1の通りです。

表1 環境 / 利用製品およびソフトウェア
OS / Windows 10
ブラウザ / Google Chrome
開発ツール実行環境 / Node.js(version 6.9.5)
パッケージ管理 / npm(version 3.10.10)

 SPAはWeb標準技術(HTML、JavaScript、CSS)で構成されているため、環境依存がほとんどありません。Node.jsやnpmは公式サイトから各OSに対応したインストーラーが取得できますので、環境の構築も容易です。また開発用のエディタとしては、近年はAtomやVisual Studio CodeといったOSSエディタが充実しているので、Node.jsとエディタの2つをインターネットから取得すれば、すぐにSPA開発を始めることができます。

●スターターツールとは

 SPA開発を始めるに当たり、さまざまなライブラリを組み合わせる必要があります。本来であれば、ライブラリを選定し、環境を構築するところから始める必要がありますが、今回は開発を簡単に始めるため、Reactは「create-react-app」、Angular2では「Angular CLI」と呼ばれるスターターツールを使用します。

 これらのツールを使うことで、開発に必要なライブラリが組み込まれたプロジェクトのひな型を簡単に作成できます。

 まずは、このスターターツールを利用し、ReactとAngular2の開発準備を行います。

●スターターツールでReactをインストール

 npmを使って「create-react-app」をインストールします。コマンドラインから、以下のコマンドを実行します。

C:\workspace>npm install -g create-react-app

 インストール完了後、以下のコマンドでバージョンが表示されることを確認してください。

C:\workspace>create-react-app -V
1.0.4

 今回使用しているツールのバージョンは「1.0.4」です。バージョンによって生成されるディレクトリの構成等が異なる可能性がありますので、ご注意ください。次にプロジェクトの作成を行います。プロジェクト名は「todo-react」とし、任意のフォルダ内で以下のコマンドを実行します。

C:\workspace>create-react-app todo-react
……
Success! Created todo-react at C:\Users\dcs\workspace\ang\todo-react


 しばらく待つと、プロジェクトの作成が完了しますので、アプリを立ち上げます。

C:\workspace>cd todo-react
C:\workspace\todo-react>npm run start
Compiled successfully!

 ブラウザで「http://localhost:3000」にアクセスすると、アプリケーションの初期ページが表示されます。

 初期のディレクトリ構成は下記のようになっています。

todo-react/
├ node_module/……npmでインストールしたモジュールを格納
├ public/……ローカルサーバに公開するファイルを格納するディレクトリ(HTMLなど)
├ src/……アプリケーションのソース格納ディレクトリ
├ .gitignore……Gitへのコミット対象から除外するリソースを設定
├ package.json……外部ライブラリへの依存関係を記したjsonファイル
└ README.md……プロジェクトの説明を記載するマークダウンファイル


●スターターツールでAngular2をインストール

 Angular2でも「create-react-app」と同様、まずはnpmを使って「Angular CLI」をインストールします。コマンドラインから、以下のコマンドを実行します。

C:\workspace>npm install -g angular-cli

 インストール完了後、以下のコマンドでバージョンが表示されることを確認してください。

C:\workspace>ng -v
angular-cli: 1.0.0-beta.26
node: 6.9.5
os: win32 x64


 今回使用するバージョンは「1.0.0-beta.26」です。次に、プロジェクトの作成を行います。プロジェクト名は「todo-angular2」とします。任意のフォルダ内で以下のコマンドを実行します。

C:\workspace>ng new todo-angular2
……
Project 'todo-angular2' successfully created.


 しばらく待つと、プロジェクトの作成が完了しますので、アプリを立ち上げます。

C:\workspace>cd todo-angular2
C:\workspace\todo-angular2>ng serve
……
webpack: Compiled successfully.


 ブラウザで「http://localhost:4200」にアクセスすると、Angular CLIで構築したアプリケーションの初期ページが表示されます。

 初期のディレクトリ構成は下記のようになっています。

todo-angular2/
├ e2e/……E2Eテスト実施スクリプトの格納ディレクトリ
├ node_module/……npmでインストールしたモジュールを格納
├ src/……アプリケーションのソース格納ディレクトリ
├ .editorconfig……プロジェクトのコーディングスタイルを定義するファイル
├ .gitignore……Gitへのコミット対象から除外するリソースを設定
├ package.json……外部ライブラリへの依存関係を記したjsonファイル
├ angular-cli.json ……アプリケーションの構成情報を設定
├ tslint.json……TypeScript用の静的解析ツールであるtslintの設定ファイル
├ karma.conf.js……単体テスト実行用テストランナーであるKarmaを実行するための設定ファイル
├ protractor.conf.js……SeleniumベースのE2EテストフレームワークであるProtractorを実行するための設定ファイル
└ README.md ……プロジェクトの説明を記載するマークダウンファイル


 その他、Angular CLIを使うことで、Angular2アプリケーションの構成要素であるコンポーネントを始めとした、各モジュールのひな型の生成をコマンドラインから行うことができます。

●サンプルアプリの全体構成(共通)

 次に、サンプルアプリをベースにReact とAngular2を比較していきます。作成するのは、「TODOを登録する」アプリで、React、Angular2のどちらでも、UIと動作が同一となるようにしています。

 アプリの作りとしては、テキストボックスにTODOを入力し、「登録」ボタンを押下すると画面下部にTODOが追加されるアプリケーションです。各TODOはクリックすることで完了済み(取り消し線)の状態になるようにしています。


 次に、画面の構成について説明します。React、Angular2どちらもコンポーネント指向のFWなので、アプリケーションをコンポーネントの集合として実装していきます。

 個々の部品をコンポーネントとして分割していくことで、より柔軟でメンテナンスしやすいアプリケーションを開発できます。

 続いて、アプリケーションを実装するに当たり、ReactとAngular2のソースコード上での比較を行います。今回は、基本的なコンポーネントの作成方法として、TODOリストの描画用である以下3つのコンポーネントについて比較してきます。

・Todoコンポーネント
・TodoListコンポーネント
・Appコンポーネント

○Reactアプリのディレクトリ構成

 srcディレクトリ内に、コンポーネントごとにファイルを分けています。

todo-react/src
├ components
| ├ Add.js……Addコンポーネント
| ├ App.js……Appコンポーネント
| ├ Todo.js……Todoコンポーネント
| └ TodoList.js……TodoListコンポーネント
├ styles/……CSSファイルを格納
└ index.js……アプリケーションのエントリーポイント

○Angular2アプリのディレクトリ構成

 Angular2で作成するに当たって、「src/app」内にコンポーネントごとにディレクトリを作成し、servicesディレクトリ内に、コンポーネント共通で利用するロジックをまとめています。

todo-angular2/src/app
├ add/
| ├ add.component.html……Addコンポーネント(画面描画)
| └ add.component.ts……Addコンポーネント(ロジック)
├ todo/
| ├ todo.component.html……Todoコンポーネント(画面描画)
| └ todo.component.ts……Todoコンポーネント(ロジック)
├ todo-list/
| ├ todo-list.component.html……TodoListコンポーネント(画面描画)
| └ todo-list.component.ts……TodoListコンポーネント(ロジック)
├ services/
| └ todo.service.ts……各コンポーネントから呼び出される共通ロジック
├ interface/
| └ todo.ts……TODO情報格納用インタフェース
├ app.module.ts……アプリで利用するモジュール群の定義ファイル
├ app.component.html ……Appコンポーネント(画面描画)
└ app.component.ts ……Appコンポーネント(ロジック)
※CSSおよびテストファイルの表記は省略

○開発言語

 SPAの開発には、従来のJavaScriptの構文をより高度化させるためのライブラリを使用することが多く、有名なものとして「Babel」「TypeScript」が挙げられます。これらのライブラリを使用することで、JavaScriptの最新仕様であるECMAScript 2015を使った開発が可能となり、Class構文や定数、arrow関数などが使えるようになります。

 今回、Reactではスターターツールに組み込まれている「Babel」を使用します。Babelは純粋なECMAScript 2015で書かれたコードを従来のコードに変換する機能を提供します。

 Angular2はスターターツールに組み込まれている「TypeScript」を使用します。「Babel」と同様、ECMAScript 2015を使った開発が可能になるのに加え、静的型付けを提供するため、コンパイラを用いて開発時のあいまいさを回避することが可能です。


表2 利用する開発言語
フレームワーク / 利用ライブラリ / 特徴
React / Babel / Class構文や定数、arrow関数などのECMAScript 2015で追加される機能が利用できる
Angular2 / TypeScript / ECMAScript 2015の機能が利用できる。また、静的型付けを提供しており、型チェックが可能となる


 なお、React開発にはBabelが用いられることが多く、Angular2はTypeScriptが標準となっていますが、ReactでTypeScriptの使用、Angular2でBabelの使用も可能です。

 続いて、ReactとAngular2それぞれのコンポーネントの実装方法を説明します。

●Reactコンポーネントの作成

Todoコンポーネント

 まず、ReactではTODOリストの各行を生成しているTodoコンポーネントは次のようになっています。

*Todo.js
--
import React from 'react';
import '../styles/todo.css';

// コンポーネントを宣言
const Todo = (props) => {
// 親コンポーネントから値を受け取る
const { todo, checkTodo } = props;
// チェック済みのTODOか判定
if (todo.isChecked) {
return (
<li className="checked" onClick={() => checkTodo(todo.id)}>{todo.text}</li>
)
} else {
return (
<li onClick={() => checkTodo(todo.id)}>{todo.text}</li>
)
}
};
// コンポーネントをエクスポート
export default Todo;
--

 冒頭にJavaScriptでは見慣れない「import」宣言があります。こちらはECMAScript 2015で策定されている仕様で、外部のファイルを参照して利用でき、JavaScriptでの大規模開発がし易くなります。もちろん、そのままではブラウザ上では動作しませんので、スターターツールに組み込まれているモジュール管理ライブラリ「webpack」がファイル間の依存関係を解決し、1つのJavaScriptファイルにバンドルします。

 TODOのテキスト部分は親から受け取った値を表示するようになっており、親からデータを受け取る「props」と呼ばれる仕組みを使って、親コンポーネントから子コンポーネントへデータの受け渡しを行います。



 親のTodoListコンポーネントから「todo」オブジェクトの形でデータを受け取り、todoオブジェクト内に格納されている「todo.text」にアクセスし、テキストデータを取り出しています。

*Todo.js ※JSX部分を抜粋
if (todo.isChecked) {
return (
<li className="checked" onClick={() => checkTodo(todo.id)}>{todo.text}</li>
)
} else {
return (
<li onClick={() => checkTodo(todo.id)}>{todo.text}</li>
)
}



 コンポーネントのView部分の実装は「JSX」と呼ばれるHTMLライクな構文を使って実装します。JSXで記述したコードをReactがDOMに変換し、画面に出力します。JSXではHTMLとほぼ同じタグが記述可能ですが、あくまでHTMLライクな構文であるため注意が必要です。

 TODOのリスト部分は単純なul/liタグを使ってリストを作成しているため、子のTodo.jsでは中身のliタグを生成する作りになっています。親であるTodoList.jsからもらったTODOのデータを使い、liタグを生成します。今回は未チェックとチェック済みのTODOで表示を切り替えるため、「isChecked」フラグを使ってJSXの出し分けを行っています。

 また、HTMLと異なる点の1つとして「className」があります。JSXではclassを指定するときにはclassNameと記述することで出力されるHTMLにclassを適用できます。

○TodoListコンポーネント

 Todo.jsコンポーネントの親であるTodoListコンポーネントは以下の通りです。

*TodoList.js
--
import React from 'react';
// コンポーネントを読み込み
import Todo from './Todo';
import '../styles/todoList.css';

const TodoList = (props) => {
// 親コンポーネントから値を受け取る
const { todos, checkTodo } = props;
const list = [];
// TODOデータの数だけリストを作成
todos.forEach((todo, index) =>
list.push(<Todo key={index} todo={todo} checkTodo={checkTodo} />
));
return (
<ul className="todoList">
{list}
</ul>
);
}

// コンポーネントをエクスポート
export default TodoList;
--

 子であるTodoコンポーネントをimportし、for文でTODOの数だけTodoコンポーネントを描画します。

Appコンポーネント

 最後に、アプリケーションの最上位の親コンポーネントにあたるAppコンポーネントを見てみます。

*App.js
import React, { Component } from 'react';
//コンポーネントを読み込み
import Add from './Add';
import TodoList from './TodoList';
import '../styles/index.css';

class App extends Component {
constructor(props) {
super(props);
// 関数のバインド
this.addTodo = this.addTodo.bind(this);
this.checkTodo = this.checkTodo.bind(this);
// stateの初期化
this.state = {todos: []};
}

// TODO追加アクション
addTodo(text) {
const { todos } = this.state;
this.setState(
{
todos: [...todos, { id: todos.length, text: text, isChecked: false }]
}
);
}

// TODOチェックアクション
checkTodo(id) {
const todos = [...this.state.todos];
todos.forEach((todo) => {
if (todo.id === id) {
todo.isChecked = !todo.isChecked;
}
});
this.setState(
{ todos: todos }
);
}

render() {
const { todos } = this.state;
// AddコンポーネントとTodoListコンポーネントの配置
return (
<div className="App">
<Add addTodo={this.addTodo} />
<TodoList todos={todos} checkTodo={this.checkTodo} />
</div>
);
}
}

export default App;



 Appコンポーネントでは「state」と呼ばれる機能を使ってTODOデータを管理し、子コンポーネントへデータの受け渡しを行っています(データの管理について詳しくは第3回で説明します)。

 画面を構成しているAddコンポーネントとTodoListコンポーネントを配置し、TODOのデータは「todos」オブジェクトにデータを詰めてTodoListコンポーネントへ渡します。

●Angular2コンポーネントの作成

 続いて、Angular2で実装した場合の各コンポーネントの作りを説明します。

 Angular2でコンポーネントを定義するに当たっては、Viewを定義するHTMLファイルと、HTML内で利用するオブジェクトやメソッドを定義するTypeScriptファイルを分けて作成しています。

○Todoコンポーネント

 まず、Todoコンポーネントについてです。

todo.component.html
<li (click)="checkTodo(todo)" [class.checked]="!todo.enabled">{{todo.text}}</li>



 Angular2では、通常のHTMLファイル内にAngularの構文を追加することが可能です。このHTMLファイルの中では、下記のAngular2の構文を利用しています。

・(イベント名)="式"指定したDOMイベントが発生した際に、式が呼び出される
・[class.クラス名]="式"式の真偽値に応じて、指定したクラスの追加/削除を行う。今回はTODOが未チェックかチェック済みかで表示を切り替えるため、todoオブジェクトの内部プロパティを元にCSSクラスの切り替えを行っている
・{{式}}式の結果を文字列として出力するための構文です。ここでは、todoオブジェクト内に格納されている「todo.name」にアクセスし、テキストデータを取り出している

 HTML内で使用しているtodoやcheckTodoは、HTMLファイルとひも付いているTypeScriptファイル内でプロパティとして定義しています。


*todo.component.ts

import { Component,Input } from '@angular/core';
import { Todo } from '../todo';
import { TodoService } from '../services/todo.service';
@Component({
selector: 'todo',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.css']
})
export class TodoComponent {
// 親コンポーネントから値を受け取る
@Input() todo: Todo;
// コンストラクタ内でDIの仕組みを利用してサービスクラスを取得
constructor(private service: TodoService) {
}
// サービスクラスを経由しチェックアクションを実行
checkTodo(todo: Todo): void {
this.service.checkTodo(todo);
}
}



 プロパティ「todo」については、親コンポーネントであるAppやTodoListから渡される値です。Angular2において、子コンポーネント内で親コンポーネントから渡された値を参照する際は、@Input()修飾子を使用します。

 checkTodoメソッド内で利用しているserviceオブジェクトは、アプリで保持している全てのTODO情報とその状態を変化させるメソッドをまとめて定義しており、DI (Dependency Injection)の仕組みを利用して取得しています。

 DIとは、依存しているモジュールの実体を実行時に外部から注入する仕組みのことで、効果的に使うことでモジュール間の依存を弱めることが可能です。

 より詳しいDIの説明については、連載第3回で説明します。

○TodoListコンポーネント

 Todoコンポーネントの親であるTodoListコンポーネントは以下の通りです。Reactと同様、保持しているTODOごとにTodoコンポーネントを繰り返し描画する役割を持ちます。

*todo-list.component.html
<ul>
<todo *ngFor="let todo of todos" [todo]="todo"></todo>
</ul>


 Angular2では、繰り返し項目の操作用APIである*ngFor属性を使って、todosで定義された配列の要素ごとに繰り返しTodoコンポーネントを描画しています。

 TodoListコンポーネントでは、子であるTodoコンポーネントへのデータの受け渡しを行っています。Angular2では、親コンポーネントのタグ内に[プロパティ]="値"のように記載することで、子コンポーネントのプロパティに対して、任意の値を渡すことができます。ここでは、todos配列の各要素であるtodoを渡しています。

import { Component, Input} from '@angular/core';
import { Todo } from '../todo';
@Component({
selector: 'todo-list',
templateUrl: './todo-list.component.html',
styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent {
// 親コンポーネントから値を受け取る
@Input() todos: Todo[];
}


 todosは、プロパティとして用意している、Todoインタフェースの配列です。todosについても、todoListの親コンポーネントであるAppから受け取るため、「@Input()」修飾子を付けています。

Appコンポーネント

 Appコンポーネントは以下の作りになっています。

*app.component.html
<add></add>
<todo-list [todos]="todos"></todo-list>


*app.component.ts
import { Component } from '@angular/core';
import { Todo } from './todo';
import {TodoService} from './services/todo.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
providers: [TodoService],
styleUrls: ['./app.component.css']
})
export class AppComponent {
// 子コンポーネントに渡すためのプロパティを定義
todos:Todo[];
constructor(private service: TodoService) {
// 実体はサービスクラスから取得
this.todos = service.todos;
}
}



 todosは、DIの仕組みを用いてTODOリストの情報を保持するserviceオブジェクトから取得し、プロパティにセットしています。

●次回は、状態(データ)管理やスタイルの適用、テストで比較

 今回は、Reactと、Angular2について、TODOアプリケーションの開発を進めながら、プロジェクトの生成と各コンポーネントの実装方法を比較しましたが、いかがだったでしょうか。

 React、Angular2、いずれもコンポーネント指向のフレームワークなので、コンポーネント単位で開発し、コンポーネントを組み合わせて画面を構築する、といった開発の流れが共通しています。

 一方でコンポーネントの作りにおいては、ReactはView部分がJSXと呼ばれるHTMLライクな構文でJavaScript内にロジックと一緒に記載するのに対し、Angular2ではView部分のHTMLファイルとロジック部分のTypeScriptファイルを分けて作成できるなどの違いが見られました。

 また、いずれも言語のベースはJavaScriptですが、純粋なECMAScript 2015であるBabelに対して、型付けのあるTypeScriptでは構文も大きく異なります。

 次回は、アプリケーションの状態(データ)管理や、CSSを用いたスタイルの適用、テストといった観点で引き続きReact、Angular2を比較していきます。お楽しみに。

[千葉達仁, 日野達也,三菱総研DCS]

最終更新:4/12(水) 7:10

@IT