Angular已经来了,又是时候放下一切来学一些新东西了。好消息是,和 React 以及 Angular 1.x版本一样,Angular 2将会流行一段时间,因此学习使用这个新框架来更加有效率的工作是一个不错的打算,值得去学习。React 最近非常火热,原因在于使用它编写现代web apps的方式非常不错。这篇文章主要是为了那些使用 react 仍然一头雾水渴望去轻松过度到使用Angular 2写代码的 react 开发者准备的。
起步
React 和Angular 2 都可以用普通的ES5语法来写,但是大多数人使用JSX + ES2015来写React,而且Angular 2 推荐使用Typescript。
TypeScript = ES2015 + 可选类型
这些技术可以在任何浏览器中运行,所以我们必须准备好构建步骤编译到浏览器对应的代码。 Webpack 或者 Gulp对打包,构建我们的项目是非常流行的。一旦你经过了构建之后,我们就能启动我们的应用。 引导我们的第一个组件,这两个框架显然会有所不同。 如果你只是想快速的入门, 看看我们在Github上的文档 Angular 2 Webpack 起步 。
React
npm install react react-dom core-js
import 'core-js' // polyfills
import React, {Component} from 'react'
import {render} from 'react-dom';
class App extends Component {
render() {
return (
<h1>hello world</h1>
);
}
}
render(<App />, document.findElementById('app'));
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
Angular 2
npm install --save @angular/core @angular/platform/browser-dynamic @angular/compiler @angular/common core-js zone.js rxjs
// polyfills
import 'core-js';
import 'zone.js/lib/browser/zone-microtask';
import 'zone.js/lib/browser/long-stack-trace-zone';
// @angular libs
import {Component} from '@angular/core';
import {bootstrap} from '@angular/platform/browser-dynamic';
@Component({
selector: '#app',
template: '<h1>hello world</h1>'
})
class App {}
bootstrap(App);
<body>
<div id="app "></div>
<script src="bundle.js "></script>
</body>
概念上没变,顶多有一点小改动需要引入bootstrap的一个简单的Hello World组件。Angular 2需要运行所需配置的Polyfill列表,其中有一些已经常驻浏览器了。让我们略过这两个文件之间的差异。试一下,我们使用OOP,面向对象编程,使用扩展来配置我们的App类的组件类。对于Angular2,我们使用组件继承组件架构静态读取我们的元数据(选择器和模板),以及我们App类中API的生命周期。 Angular2的API与我们的应用程序代码和框架更为分离。虽然使用Angular2的架构和扩展有本质的不同,但他们都具有相同的目标,那就是允许我们使用框架中提供的方法。选择器可以是任何有效的CSS选择器,包括标签名称,所以我们可以认为它是 document.querySelector(selector) 在这里 document是父组件,selector是我们的组件。模板则利用ES2015的多行字符串而不是JSX。
存储状态
现代web开发就是管理我们的应用程序的状态。 在这个例子中, 我们通过使用本地状态来简化应用的状态管理而不是像 [插入 Flux 库],关于如何存储状态有许多不同的观点.。我们将更多地讨论组件的状态,而不是应用程序状态。
React
class App extends Component {
state = {message: 'hello'}; // be sure to have proper plugin for babel
render() {
return (
<div>{this.state.message}</div>
);
}
}
Angular 2
@Component({
selector: '#app',
template: '<div>{{ state.message }}</div>',
})
class App {
state = {message: 'hello'};
}
这里没有太大的区别。Reactor和Angular这两个例子都是标准的初始化状态的方式。 有一点改变的是模板插值。 React 使用一个{ } 而Angular2坚持它的起源, 使用{{ }}。 请注意, Angular 2有许多方法初始化状态, 这种方法跟React最相似。
事件处理和状态变更
React使用 JSX而 Angular 2使用HTML,两者之间在组件中处理触发的DOM事件这部分有一些差异。
React
class App extends Component {
state = {count: 0}; // be sure to have proper plugin for babel
handleClick(e) {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div>
<button onClick={this.handleClick.bind(this)}>count</button>
{this.state.count}
</div>
);
}
}
Angular 2
@Component({
selector: 'app',
template: `
<div>
<button (click)="handleClick($event) ">+1</button>
{{ state.count }}
</div>
`,
})
class App {
state = {count: 0};
handleClick(e) {
this.state.count++;
}
}
然而 React 将JSX组件的props(属性)映射到了 DOM 事件上,Angular 2直接使用DOM事件。让我们看看Angular 2在语法上是怎么实现的。(click)告诉angular 我们想要在目标元素上绑定click事件以及运行对应的事件处理函数。尽管这看上去很有趣,他们确实是有效的HTML。现在已经不是2003年了,我们再也不能检查HTML是不是有效的了。如果你只是不能忍受使用这种语法,你可以把(click)换成 on-click。所有事件都可以这样写,不仅仅是click事件。现在我们调用事件处理函数 handleClick()。注意,我们必须执行回调函数,这和React不一样,React 保留着对回调函数的引用。这是因为 React 将事件处理器注册为事件事件的回调函数(这里react将this绑定到这个组件上),而 Angular 2 将组件作为上下文执行一段表达式。在 React 中,如果 state 变了,调用 setState() 方法可以触发组件的更新,组件会重新渲染。Angular 2也是这样,但是你可以直接改变state而不用像react那样使用一个方法来做到这一点,这是因为 Angular 框架会时刻观察组件树属性的任何变动。
数据绑定与单向数据流
使用单项数据流的一个原因是它使得应用的状态更加简单, 但有时你需要双向数据流。 这不会比在处理表单和用户输入或任何其他有状态的组件更加具有说明性。
React
class App extends Component {
constructor() {
super();
this.state = {text: ''};
}
onChange(e) {
this.setState({text: e.target.value});
}
render() {
return (
<div>
<input onChange={this.onChange.bind(this)} value={this.state.value} />
{this.state.text}
</div>
);
}
}
Angular 2
@Component({
selector: 'app',
template: `
<div>
<input (change)="onChange($event)" [value]="state.text" >
{{state.text}}
</div>
`,
})
class App {
state = {text: ''};
onChange(e) {
this.state.text = e.target.value;
}
}
处理输入变化的基本方法在两个框架之间没有太大区别。都是绑定事件,然后基于输入的值更改状态。 最主要的区别是它们绑定事件的方式。 Angular 2 允许绑定到从组件(如浏览器本地或自定义事件)发出的任何事件。 在Angular 2中,我们必须通过的 $event 变量。在 Angular 1.x中,$event 变量是框架内部创建的。 React 绑定onChange 事件是内部绑定到 keyup事件。 Angular
2 带来的 ngModel可以让双向数据绑定更加容易。 我们也可以重构 Angular 2 例子,在模板中更多的利用局部变量。在React的组件中, 我们可以把局部变量看作是在我们的渲染函数中局部生存的变量。
@Component({
selector: 'app',
template: `
<div>
<input (change)="state.text=$ event.target.value " [value]="state.text " >
{{state.text}}
</div>
`,
})
class App {
state = {text: ''};
}
引用
随着 React 0.14稳定版的出现, 我们可以使用一种新的方法来处理引用。 让我们来比较一下两个框架如何处理引用。
React
class App extends Component {
changeColor() {
this.box.classList.add('yellow');
}
render() {
return (
<div>
<button
onClick={this.changeColor.bind(this)}
ref={node => this.box = node}
>
change
</button>
<div className="box "></div>
</div>
}
}
Angular 2@Component({
selector: 'app',
template: `
<div>
<button (click)="changeColor(box)">change</button>
<div class="box" #box></div>
</div>
`,
})
class App {
changeColor(box) {
box.classList.add('yellow')
}
}
在Reactor中,我们可以获得原生的DOM节点并将其赋值给组件本身的一个属性。在Angular2中我们在需要引用的元素中使用一个#后面跟一个变量名称来达到引用的目的。 这个引用是本地的模板,所以我们必须将它作为一个参数来获取它。 我们可以思考为什么Angular团队会选择#号。 除非给我们的元素一个全局范围内唯一的id来让我们访问DOM元素。 我们可以获得 div 引用通过window.myElement的形式 (如果你不知道这个
你应该试试看)。像React,这个引用就是一个原生的DOM。
组件组成
这两个框架都是通过组件的形式来构建Web应用的,所以创建更多的组件是必不可少的。 让我们看一看。
class Sidebar extends Component {
render() {
return (
<div className="sidebar ">
<h1>Side bar</h1>
</div>
);
}
}
class App extends Component {
render() {
return (
<div>
<Sidebar />
</div>
);
}
}
Angular 2
@Component({
selector: 'sidebar',
template: `
<div class="sidebar ">
<h1>Side bar</h1>
</div>
`
})
class Sidebar {}
@Component({
selector: 'app',
directives: [
Sidebar // we must declare which components/directives are in our template
],
template: `
<div>
<sidebar></sidebar>
</div>
`,
})
class App {}
在Angular 2中,我们使用@Component装饰器来声明一个组件,比如 App。在 @Component装饰器中可以用同样的方式定义模板。 然后我们必须将新的组件添加到容器组件的directives属性中以便我们在模板中使用它。 我们这样做的原因是因为,不像JSX,只是编译到函数调用。Angular 2 模板需要告诉他们模板中使用了哪些组件,因为我们只是使用字符串来编写模板。 这在将来测试的时候会非常方便,因为我们可以模拟其它的组件 (在React
我们期待做这样的模块)。 当然,我们可以在React中使用<Sidebar />自关闭标签。在Angular 2中我们暂时还不能这样做.
属性
无状态组件的概念使事情变得灵活和可预测。通过将状态从顶级组件传递到子组件,我们只需要通过组件上的props来关注无状态组件的输入。
React
class Sidebar extends React.Component {
render() {
return (
<div className="sidebar ">
<h1>{this.props.title}</h1>
</div>
);
}
}
class App extends React.Component {
constructor(){
super();
this.state = {title: 'Sidebar'};
}
render() {
return (
<div>
<Sidebar title={this.state.title} />
</div>
);
}
}
Angular 2
import { Component, Input } from '@angular/core';
@Component({
selector: 'sidebar',
template: `
<div class="sidebar">
<h1>{{ title }}</h1>
</div>
`
})
class Sidebar {
@Input() title:string;
}
@Component({
selector: 'app',
directives: [
Sidebar
],
template: `
<div>
<sidebar [title]="state.title"></sidebar>
</div>
`,
})
class App {
state = {
title: 'Side bar'
}
}
首先要注意的是,我们必须导入@Input() 装饰器。 不像@Component()是我们类的装饰器, @Input()是我们类中属性的装饰器,所以它的位置在类中属性名称的前面。 我们也使用了typescript类型声明类定义title的类型为 string 类型。 现在我们在模板中可以像这样 {{ title }}来引用这个属性。在容器组件中, 我们可以使用在类中提供的state上使用括号语法(angular2的属性绑定语法)来给title提供值, [title]="state.title"。
动态元素
在JSX中,我们可以在你的模板中使用去遍历集合和用来进行JSX的输出渲染校验。 Angular 2采用类似 angular 1.x 的方式来处理这些(ng2中使用内置的ngFor指令,ng1中使用ng-repeat)。
值得注意的是 Angular 2不支持JSX,但是正在社区寻找能与他们合作的人。
React
class Sidebar extends Component {
render() {
return (
<div className="sidebar ">
<h1>{this.props.title}</h1>
<ul>
{this.props.items.map(item => <li>{item}</li>})}
</ul>
</div>
);
}
}
class App extends Component {
constructor(){
super();
this.state = {
title: 'Sidebar',
navItems: ['home', 'profile', 'settings']
};
}
render() {
return (
<div>
<Sidebar
title={this.state.title}
items={this.state.navItems}
/>
</div>
);
}
}
Angular 2
@Component({
selector: 'sidebar',
template: `
<div class="sidebar">
<h1>{{ title }}</h1>
<ul>
<li *ngFor="#item of items">{{ item }}</li>
</ul>
</div>
`
})
class Sidebar {
@Input() title:string;
@Input() items:string[];
}
@Component({
selector: 'app',
directives: [
Sidebar
],
template: `
<div>
<sidebar
[title]="state.title"
[items]="state.items"
></sidebar>
</div>
`,
})
class App {
state = {
title: 'Side bar',
items: ['home', 'profile', 'settings']
}
}
类似 angular 1.x, angular 2也有一个指令用于循环数据并创建页面元素。 ngFor指令有一个类似的姊妹指令为ng-repeat。 因为性能原因,angular 2 使用<template> 去克隆元素, 它们创建了一种语法糖的形式 *。 这就是我们为什么使用 *ngFor。就像使用引用的时候,我们使用#号后面跟一个变量名来创建本地变量一样。类似ES2015
iterators(迭代器), 我们使用 for of去迭代一个集合,然后分别赋值给我们创建的这个本地变量。
Angular 2
<div class="sidebar">
<h1>{{title}}</h1>
<ul>
<template ngFor #item [ngForOf]="items">
<li>{{ item }}</li>
</template>
</ul>
</div>
样式
有很多添加样式到组件的方法。随着webpack等构建工具的出现,方法也越来越多。 CSS封装是创建灵活的组件的一个重要组成部分。 React 不提供一个内置的解决方案, 所以社区加强了 CSS模块。 这些样式都在组件范围内使用唯一的类型来避免冲突。 你还需要设置你的构建工具来处理这个。
React
import CSSModules from 'react-css-modules';
import styles from './styles.css'; // string
import React, {Component} from 'react';
class App extends Component {
render() {
return (
<div styleName='app'> {/* creates app__2gft5 */}
<div styleName='actions'> {/* creates app__actions__74tr */}
<button styleName='button'>click</button>
</div>
</div>
);
}
}
const Root = CSSModules(App, style);
render(<Root/>, document.findElementById('app));
Angular 2
@Component({
selector: 'app',
styles: [`
.app {
color: red;
}
.app.actions {
border: 1px solid yellow;
}
.app.actions.button {
padding: 10px;
}
`],
template: `
<div class="app ">
<div class="actions ">
<button class="button ">click</button>
</div>
</div>
`,
})
class App {}
bootstrap(App);
在Component装饰器中 中使用 styles数组 ,我们可以将样式附加到目标组件。 像 CSSModules,使用命名空间来避免类名冲突。因为样式只是一个字符串, 我们可以做很多事情,如动态差值。
const APP_CLASS = 'app';
@Component({
selector: 'app',
styles: [`
.${APP_CLASS} {
}
.${APP_CLASS}.actions {
}
.${APP_CLASS}.actions.button {
}
`],
template: `
<div class="${APP_CLASS}">
<div class="actions">
<button class="button">click</button>
</div>
</div>
`,
})
class App {}
bootstrap(App);
这些天使用正规的css,以及使用像 webpack这样的构建工具, 以及可以使用预处理器。
import styles from './style.less';
@Component({
selector: 'app',
styles: [styles],
template: `
<div class="app ">
<div class="actions ">
<button class="button ">click</button>
</div>
</div>
`,
})
class App {}
如果你不喜欢angular为组件计算出来的样式类名称,那么你可以选择更改。
import { Component, Input, ViewEncapsulation} from '@angular/core';
import styles from './style.less';
@Component({
encapsulation: ViewEncapsulation.Native,
selector: 'app',
styles: [styles],
template: `
<div class="app ">
<div class="actions ">
<button class="button ">click</button>
</div>
</div>
`,
})
class App {}
在Component 装饰器中的encapsulation 属性允许我们更改样式行为。 首先我们需要导入ViewEncapsulation。 有三个值,我们可以使用。默认值是 ViewEncapsulation.Emulated 它输出的是带命名空间的(css)类名称。 ViewEncapsulations.Native使用影子DOM(所有样式只在该DOM中有效)。ViewEncapsulation.None 所有组件范围内的样式都会生效(意思就是没有隔离,可能会导致样式冲突,一般情况下,后面加载会覆盖前面定义的)。
Angular 2的api非常稳定,不会在短时间内发生任何重大变化。就当前的语法来说, 构建Web应用程序的方法和Ract没有什么太大的不同。 他们都完成同样的事情。 Angular 2本身提供的更多一些。 我们不去谈论生命周期事件, actions(动作), 以及对它们对状态的先进的管理 。请继续关注。与此同时,看看我们的 在Github上Angular 2 Webpack Starter ,速度最快,最好的方法开始使用Angular2。