为React用户提供的Angular2入门指南
对于使用过React的人来说,我将总结React中的某些概念在Angular 2中的对应关系是怎样的。作为一个之前使用React的用户,当我开始学习Angular 2时,我希望能有这样的信息。
顺便说一句,因为要称其为Angular而不是Angular2。所以下文中提到的Angular指的是Angular 2.x。
应用程序的启动
回应
<!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中,使用ReactDOM.render来指定应用程序的根组件和目标DOM元素。
角度
<!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’,Loading… 会被替换为
hello world
。
成分
回应
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 的组件!另外,componentDidMount 等生命周期方法、props、props.children、state 这些都是代表性的 React 组件功能吧。
角度
正如之前的例子所示,Angular中的HTML代码不再写在React的render方法中,而是写在@Component装饰器中。通过设置templateUrl: ‘app.component.html’,还可以将HTML文件与代码分离。CSS也是类似的。
在React中,可以直接使用类名来引用组件,如,但在Angular中,选择器中指定的字符串将替换该组件。
由于Angular也有类似于React的props、props.children、state和生命周期方法等功能,让我们一一来看一下。
道具
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' });
}
}
母組件→子組件
要将数据从父组件传递给子组件,需要在子组件上定义一个带有 @Input 注解的属性。父组件通过使用 [title]=”‘hello'” 的形式来指定要传递的属性,例如 。这就是属性绑定的方式。
我知道你可能会对 中的 “‘hello'” 表示法感到困惑,但实际上这是 React 中的 的写法。在 React 中,我们可以使用 {} 来编写 JavaScript 表达式,而在 Angular 的绑定中,双引号中的内容就会被当作 JavaScript 处理。所以当我们想传递一个普通的字符串时,就需要写成 “‘hello'”。
子组件→父组件
为了将事件从子组件传递给父组件,在子组件中定义一个具有@Output()注释的EventEmitter属性,例如子组件中的@Output() onHello = new EventEmitter(); 然后在父组件中使用的方式,通过用()括起来指定。 handleHello是父组件类中的方法。 $event是一个特殊的符号,它包含事件的消息或负载。在这个例子中,它包含一个名为{ ‘i said’: ‘hello’ }的JSON对象。这就是事件绑定。
除此之外,还有一些常用于表单等的Two-way数据绑定,例如。由于包含了父组件到子组件和子组件到父组件的双向绑定,[(ngModel)]表示这个意思。如果不熟悉的话可能会感到有些困惑。
组件的子元素
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,可以使用。上述示例的输出是什么。
<div class="myframe">
<p>in my frame :)</p>
</div>
在旁边,.myframe的CSS生效,文字会变成红色。
陈述
在Angular中,可以将类内的实例进行单向或双向绑定。没有什么特别要说的。
生命周期方法
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();
}
}
在Angular中也有类似于React的componentDidMount和componentWillUnmount等生命周期方法。要实现它们,可以通过将相应的生命周期混入到class MyComponent extends OnInit中,并在ngOnInit等方法中编写实现。当类看到要实现的生命周期方法时,明确起见会很好!
在这个示例中,我们订阅了Service类(稍后会提到)在Component初始化时持有的Observable,并在Component被销毁时取消了订阅。我认为注册和取消事件监听器是最先想到的用例。
在React中没有的服务
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');
}
}
在处理仅包含视图的React时,没有地方编写业务逻辑和副作用等。因此,我认为在这种情况下,许多人选择引入Flux架构。在Angular中,我们可以定义一个作为服务(Service)来处理这些东西,并可以从组件中使用它。通过在服务中存储数据,并提供获取和修改数据的方法,或者通过触发事件或可观察对象来实现数据更改的通知机制。
服务的实例化不需要手动创建,而是通过Angular的DI(依赖注入)机制来完成。我们给服务添加@Injectable注解,并在使用该服务的组件或模块中提供者(providers)属性中加入该服务。然后,我们可以通过在构造函数的参数上声明服务来获得该服务的实例。
需要注意的是,每次在providers中添加服务时,都会创建一个新的服务实例。因此,如果要使服务在整个应用程序中共享数据,我们需要将其写在应用程序的模块providers中,而不是组件中。
路由器 (lù qì)
回应
// 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 导入模块来使用。路由的定义与react-router类似,通过路径来编写组件。匹配路由的组件将在 中渲染。
在12月9日在东京举行的Angular聚会上,出现了angular/core版本2.x与angular/router版本3.x不匹配的问题。为了解决这个问题,下一个Angular的主要版本将不是3,而是4。
创建一个React应用
在中文中,类似于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 Native进行回应的位置上,有一些库,比如ionic2、OnsenUI和NativeScript。据说还有一个名为angular/react-native-renderer的库,它将React Native的渲染器与Angular连接起来。
在这些中,我稍微接触了一下ionic2,我发现ionic2提供的组件可以在Web/iOS/Android上运行,这种一次编写,到处运行的特性让我感到震撼。真是牛逼啊!
// どのプラットフォームでも動く
<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以及出现了什么错误信息并去谷歌搜索…感觉很辛苦。但在Angular 2中,这些问题好像有所减轻。现在只有ng-for和ng-if这些角色出现,并且错误信息也变得更好,大部分情况下只要读懂写的内容就能修复。
有人说JSX很糟糕,因为它只是附在类上的一个巨大的装饰器,并且模板只是一个简单的字符串,这更糟糕(省略)
以下是一种可能的汉语表达方式:
虽然模板只是一个普通的字符串,但在像WebStorm或安装了扩展的VSCode等编辑器中,它们提供了语法高亮功能,从而大大减少了困扰。
当然也可以将模板与组件分开,通过templateUrl: ‘foo.component.html’的方式进行引用。由于模板只是普通的字符串,所以在执行之前无法知道模板中是否存在错误的问题。因此,最好编写组件是否被渲染的最基本测试,或者进行AoT编译以在执行之前发现错误。(实际上,有人提到AoT是必需的)
总结
我大致介绍了React角度的基本要素。Angular本体已经稳定了,但是生态系统还没有像React那样成熟,所以目前的问题是依赖项中有很多beta版本,但是这是一个能够愉快地编写Web应用程序的框架,所以如果您还没有接触过,请务必试试!
请引用
在学习Angular入门过程中,我觉得利用Google翻译一手进行阅读官方文档是最佳选择。你可以按照《英雄之旅》教程逐步学习,通过Plunker这个能够在浏览器上运行JS应用程序的网站,每个步骤都提供了一个应用程序供你简单测试,并且你也可以下载文件压缩包,在本地使用你常用的编辑器进行修改。在下载的index.html所在的文件夹中启动http-server等工具,就可以利用system.js这个神奇的东西让Angular运行起来!顺便一提,Angular领域有一些打包器例如Webpack、Browserify、System.js等,但我觉得Webpack是最常用的工具。有个Angular的大牛曾说System.js还太早!(参考:TechFeed Live#2报告)
有一张备忘单可供使用,所以你想知道这个怎么做?还是那个是什么?→去查一下→学习进展会更顺利。
https://angular.io/docs/ts/latest/guide/cheatsheet.html