Reactはやったことある人向けに、ReactのアレはAngular2だとどうなってるの?というのをまとめていきます。Reactユーザーだった自分がAngular2始めるにあたりこういう情報あればなと思ってたやつです
ところでAngular2じゃなくてAngularと呼んでね!とのことなので、以下Angularと呼びますがAngular 2.xを指します。
アプリケーションのブートストラップ
React
<!doctype html>
<html lang="en">
<head>
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
import React from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
render() {
return (
<div>
<p>hello world</p>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
ReactではアプリケーションのルートとなるComponentと、対象となるDOM要素を指定して ReactDOM.render
ですね。
Angular
<!doctype html>
<html>
<head>
<title>Angular2</title>
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div>
<p>{{title}}</p>
</div>
`
})
export class AppComponent {
title = 'hello world';
}
Angularでは、ブートストラップ対象になるのはModuleです。Moduleは、Component、Serviceなどからなるアプリケーションの機能の単位です。 @NgModule
デコレータの bootstrap
に指定したComponentが表示されます。
この例では platformBrowserDynamic().bootstrapModule(AppModule);
で AppModule
が起動し、 bootstrap: [AppComponent]
なので AppComponent
描画され、 selector: 'app-root'
なので <app-root>Loading...</app-root>
が <div><p>hello world</p></div>
に書き換わるという感じです!
Component
React
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: [
'sleep',
'do nothing'
]
};
}
componentDidMount() {
console.log('mounted :)');
}
render() {
return (
<div className="App">
<h1>{this.props.title}</h1>
<ul>
{this.state.todos.map((todo, i) => <li key={i}><p>{todo}</p></li>)}
</ul>
<button type="button" onClick={this.props.onClick}> +1 :)</button>
{ this.props.children }
</div>
);
}
}
React.Component
を継承したクラスに render
メソッドを実装しておけばReactのComponentですね!
その他にも componentDidMount
などのライフサイクルメソッド、 props
, props.children
, state
あたりがReact Componentの代表的な機能でしょうか。
Angular
先程の例にもあるように、AngularではReactで render
メソッドに書いていたHTMLは @Component
デコレータに書きます。 templateUrl: 'app.component.html'
とすることでHTMLファイルを分離することもできます。CSSも同様です。
Reactだとクラス名がそのまま <MyClass />
のように使えましたが、Angularでは selector
に指定した文字列がそのComponentで置き換えられます。
Reactの props
, props.children
, state
, ライフサイクルメソッドに相当するような機能がAngularにもあるので、ひとつずつ見ていきましょう。
props
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<sample-child [title]="'hello'" (onHello)="handleHello($event)"></sample-child>
`
})
export class AppComponent {
handleHello(ev: any) {
console.log(ev);
}
}
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'sample-child',
template: `
<h1>{{title}}</h1>
<button type="button" (click)="clicked()">say hello</button>
`
})
export class SampleChildComponent {
@Input() title: string;
@Output() onHello = new EventEmitter<any>();
clicked() {
this.onHello.emit({ 'i said': 'hello' });
}
}
親Component→子Component
親Componentから子Componentにデータを受け渡すには、子Componentに @Input
アノテーションを付けたプロパティを用意します。親Componentは <child [title]="'hello'" />
のように []
で渡すプロパティを指定します。これがプロパティバインディングです。
<child [title]="'hello'" />
の"'hello'"
という表記が気になったかと思いますが、これはReactでいう<Child title={'hello'} />
です。Reactだと{}
の中にJSの式を書けるように、Angularのバインディングの右辺では""
の中はJSになります。 なのでただの文字列を与えたいときは"'hello'"
となります。
子Component→親Component
子から親にイベントを伝えるには、子に @Output() onHello = new EventEmitter<any>();
のように @Output
アノテーションをもった EventEmitter
のプロパティを定義し、親では <child (onHello)="handleHello($event)" />
のように ()
で囲んで指定します。 handleHello
は親Componentのクラスが持っているメソッドです。 $event
は特別なシンボルで、イベントのメッセージというかペイロードが入っています。この例では { 'i said': 'hello' }
というJSONが入っています。これがイベントバインディングです。
他にもフォームなどでよく使う <input [(ngModel)]="myName" />
とするTwo-wayデータバインディングなどがあります。親→子、子→親両方あるので [(ngModel)]
ってことですかね。なかなか慣れてないとアレな気はします
props.children
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<my-frame>
<p>in my frame :)</p>
</my-frame>
`
})
export class AppComponent {
}
import { Component } from '@angular/core';
@Component({
selector: 'my-frame',
template: `
<div class="myframe">
<ng-content></ng-content>
</div>
`,
styles: [`
.myframe {
color: red;
}
`]
})
export class MyFrameComponent {
}
自分のタグ内に定義されたHTMLを出力するには <ng-content></ng-content>
を使います。上記の例の出力は
<div class="myframe">
<p>in my frame :)</p>
</div>
となり、 .myframe
のCSSが効いて赤い文字になります。
state
Angularだと普通にクラス内のインスタンスをOne-wayなりTwo-wayバインディングする感じでしょうか。特に言うことないです。
ライフサイクルメソッド
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { SampleService } from './sample.service';
@Component({
selector: 'app-root',
template: `
<p>{{message}}<p>
<button (click)="fetchMessage()">fetch</button>
`,
providers: [
SampleService
]
})
export class LifeCycleSampleComponent implements OnInit, OnDestroy {
message: string = 'initial message';
subscription: Subscription;
constructor(private sampleService: SampleService) { }
fetchMessage() {
this.sampleService.nextMessage();
}
ngOnInit() {
this.subscription = this.sampleService.message.subscribe(message => this.message = message);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Reactの componentDidMount
や ComponentWillUnmount
といったライフサイクルメソッドに相当するものがAngularにもあります。それらを実装する場合は class MyComponent extends OnInit
のように該当するサイクルをミックスインし、 ngOnInit
メソッドなどに実装を書きます。実装するライフサイクルメソッドがクラスを見たときに明示的なのがいいですね!
この例ではComponentの初期化時にServiceクラス(後述)が持っているObservableをsubscribeし、Componentが破棄されるタイミングでsubscriptionも破棄しています。こういったイベントリスナーの登録・破棄がまずは思いつくユースケースな気がします。
(Reactにはない)Service
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
@Injectable()
export class SampleService {
private _message: Subject<string>;
message: Observable<string>;
constructor() {
this._message = new Subject<string>();
this.message = this._message.asObservable();
}
nextMessage() {
this._message.next('next message');
}
}
Viewだけを扱うReactにはビジネスロジックや副作用などを書く場所がありません。そこでFluxアーキテクチャを導入することが多いと思います。Angularではそういったものを担当するクラスをServiceとして定義し、Componentから使うことができます。Serviceにデータを持たせて、取得や変更するメソッド、もしくはデータ変更で発火するイベントやObservableを生やしておく感じです。
Serviceのインスタンス化は自分でnewせずにAngularのも持つDIの仕組みに任せます。 @Injectable
アノテーションを付け、Serviceを使うComponentやModuleから providers: [ MySerivce ]
とします。そうしてコンストラクタの引数にServiceを引数として与えればServiceのインスタンスが得られます。
注意点としては providers
に書くたびに別のServiceのインスタンスができるので、アプリケーションで共通のデータを持たせたいServiceはComponentではなくアプリケーションのModuleのprovidersに書いておかないといけません。
router
React
// github.com/ReactTraining/react-router/README.md から
render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="about" component={About}/>
<Route path="users" component={Users}>
<Route path="/user/:userId" component={User}/>
</Route>
<Route path="*" component={NoMatch}/>
</Route>
</Router>
), document.getElementById('root'))
React自体はルーティングの機能を持っていないので、react-routerといったライブラリを使用すると思います。使い方はシンプルで、 path
と component
を列挙していく感じですね。
+ import { RouterModule } from '@angular/router';
+ import { routes } from './app.routes';
@NgModule({
...
imports: [
HttpModule,
+ RouterModule.forRoot(routes)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
import { Routes } from '@angular/router';
import { TodosComponent } from './todos/todos.component';
import { UsersComponent } from './users/users.component';
export const routes: Routes = [
{ path: 'todos', component: TodosComponent },
{ path: 'users', component: UsersComponent }
];
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<a routerLink="/todos">Todos</a>
<a routerLink="/users">Users</a>
<router-outlet></router-outlet>
`
})
export class AppComponent {
title = 'root component';
}
Angularはルーティング機能も持っており、 @angular/router
からModuleをインポートして使います。ルーティングの定義はreact-routerと似た感じでパスをComponentを書いていきます。ルーティングにマッチしたComponentは <router-outlet></router-outlet>
に描画されます。
12/9に東京で開催されたAngularのミートアップでは、 @angular/core
が2.xなのに対し @angular/router
が3.xとずれている問題があり、これを解消するために次期Angularのメジャーバージョンは3ではなく4になる話が出たみたいです(発表資料)。4...!
create-react-app
create-react-appと同様のツールとしてangular-cliがあります。Typescript, Webpack, karmaなどなどの設定を済ませたAngularアプリの雛形を生成してくれます。それだけでなく、ComponentやServiceの雛形とそのテストコードを生成し、NgModuleにインポートしておいてくれるという便利機能なども付いています。
$ npm i -g angular-cli # ng コマンドが使えるようになる
$ ng new my-angular-app # Angularアプリを生成する
$ cd my-angular-app
$ ng serve # webpack-dev-serverを起動
$ ng test
$ ng e2e
$ ng build
$ ng g component my-component # generateを省略してg
installing component
create src/app/my-component/my-component.component.css
create src/app/my-component/my-component.component.html
create src/app/my-component/my-component.component.spec.ts
create src/app/my-component/my-component.component.ts
$ ng g service my-service
installing service
create src/app/my-service.service.spec.ts
create src/app/my-service.service.ts
WARNING Service is generated but not provided, it must be provided to be used
個人的にWebpackとかの設定つらいので、そういったところをこういうツールで補ってもらえるのはとてもありがたいです
react-native
Reactに対するReact Nativeと同じような位置にionic2、OnsenUI、NativeScriptなどいくつかのライブラリがあります。angular/react-native-rendererというReact NativeのレンダラとAngularをつなぐ(!?)のもあるみたいです。
この中でionic2を少し触ったのですが、ionic2が提供するComponentがWeb/iOS/Androidで動作するようになっており、learn once write anywhereではなくwrite once run anywhereなとこが衝撃的でした。ジャバか...!
// どのプラットフォームでも動く
<ion-list>
<button ion-item *ngFor="let item of items" (click)="itemSelected(item)">
<my-item>{{ item }}</my-item>
</button>
</ion-list>
番外編
Angularへの苦手意識、解消します
ng-xxxを無限に覚えるのが嫌だ
Angular 1.xをちょっとやってたときの個人的な記憶なのですが、何をするにもng-xxxを使わなければならず、ng-xxxを探して実装してバグったらエラーメッセージに出て来るng-xxxがどうしたというのをググる...という辛いものがあったのですが、Angular2ではそのへんかなり軽減されてる気がします。とりあえずの登場人物は ng-for
と ng-if
だけで、エラーメッセージも良くなっていて、だいたいは書いてあることを読めば修正できる感じです
JSXをキモイと言うが、 クラスにくっついてるクッソでかいデコレータにしかもtemplateがただの文字列で入ってるとかそっちのほうが(略
テンプレートがただの文字列なのは仕方ないのですが、webstormやエクステンションを入れたVSCodeなどではシンタックスハイライトを提供してくれるのでかなり低減されます。
もちろん templateUrl: 'foo.component.html'
としてファイルを分けてしまうのもアリです。テンプレートがただの文字列のため、実行するまではテンプレートにミスがあるかわからないという問題があります。そのためコンポーネントが描画されるかの最低限のテストを書いていくか、AoTコンパイルを行って実行前にエラーを出すなどをやったほうがいいみたいです。(そもそもAoTは必須という話も)
まとめ
Reactから見たAngularの基礎要素をざっと紹介しました。Angular本体はstableになりましたがまだまだエコシステムがReactのように成熟していないので、いまのところは dependencies
の中がbetaだらけになったりする難点がありますが、Webアプリを楽しく書けるフレームワークなので、まだ触っていない方は是非!
参考
公式ドキュメントをGoogle翻訳片手に読んでいくのがAngular入門には結局のところベストな気がします。
https://angular.io/docs/ts/latest/
Tour of heroesというチュートリアルにそって一通り学べます。Plunkerというブラウザ上でJSアプリが動作するWebサイトを使って各ステップごとのアプリが用意されているので、そこで簡単な動作確認をしつつ、ファイル一式をzipでダウンロードしローカルでいつものエディタでいじることも可能です。ダウンロードしたindex.htmlがあるディレクトリでhttp-serverなどを立ち上げればsystem.jsというすごいやつがなんやかんやしてAngularが動くぞ!
ちなみにバンドラーとしてWebpack, Browserify, System.jsなどありますが、WebpackがAngular界隈では一番使われてる気がします。System.jsはAngularのすごい人がまだ早いって言ってた!(参照: TechFeed Live#2レポート)
チートシートも用意されているので、あれどうするんだっけ?とか、これなんだ?→調べる→学びが捗ります
https://angular.io/docs/ts/latest/guide/cheatsheet.html