为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等编辑器中,它们提供了语法高亮功能,从而大大减少了困扰。

スクリーンショット 2016-12-14 23.36.01.png

当然也可以将模板与组件分开,通过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

广告
将在 10 秒后关闭
bannerAds