kittencup / angular2-ama-cn

angular2 随便问
691 stars 101 forks source link

Angular 2的核心概念 #91

Open kittencup opened 8 years ago

kittencup commented 8 years ago

原文地址: http://victorsavkin.com/post/118372404541/the-core-concepts-of-angular-2

让我们来构建一个程序

![](http://www.kittencup.com/images/2015-07-17-Angular 2的核心概念/feed.png)

组件(Component)

Angular 2的应用是由一系列的组件构成的(ui element、route..),应用始终有一个包含其他组件的根组件,换句话每个angualr2应用都有一个组件树,这个应用程序可能是这样的:

![](http://www.kittencup.com/images/2015-07-17-Angular 2的核心概念/a.png)

Application是一个根组件,Filters组件具有speaker输入框和过滤按钮,下面有一系列的talks,及每一个talk-cmp

// TalkCmp.ts
@Component({
  selector: 'talk-cmp',
  properties: ['talk'],
  events: ['rate']
})
@View({
  directives: [FormattedRating, WatchButton, RateButton],
  templateUrl: 'talk_cmp.html'
})
class TalkCmp {
  talk: Talk;
  rate: EventEmitter;
  //...
}
<!-- talk_cmp.html -->
{% raw %}
{{talk.title}}
{{talk.speaker}}
{% endraw %}
<formatted-rating [rating]="talk.rating"></formatted-rating>
<watch-button [talk]="talk"></watch-button>
<rate-button [talk]="talk"></rate-button>

属性和事件绑定

一个组件具有的属性和事件绑定,这些内容在组件的decorator定义。

@Component({
  selector: 'talk-cmp',
  properties: ['talk'],
  events: ['rate']
})
...

数据通过组件上的属性绑定流入组件,数据通过组件上的事件绑定流出组件

![](http://www.kittencup.com/images/2015-07-17-Angular 2的核心概念/b.png)

当你在应用程序中实例化一个组件则可以通过公共API使用属性和事件绑定

视图(View)

一个组件有一个视图,描述了在页面上怎么呈现组件

@View({
  directives: [FormattedRating, WatchButton, RateButton],
  templateUrl: 'talk_cmp.html'
})
<!-- talk_cmp.html -->
{% raw %}
{{talk.title}}
{{talk.speaker}}
{% endraw %}
<formatted-rating [rating]="talk.rating"></formatted-rating>
<watch-button [talk]="talk"></watch-button>
<rate-button [talk]="talk"></rate-button>

Angular 2遵循web平台标准,因此组件视图元素会在Shadow DOM内创建,如果你的浏览器不支持Shadow DOM,Angular会模拟Shadow DOM.

一个视图需要知道两件事情:模板本身及模板中可使用的指令。如在上面的例子中,你可以在外部定义模板,使用templateUrl,或内联模板template。

...javascript
@View({
  directives: [FormattedRating, WatchButton, RateButton],
  template: `
    {{talk.title}}
    {{talk.speaker}}
    <formatted-rating [rating]="talk.rating"></formatted-rating>
    <watch-button [talk]="talk"></watch-button>
    <rate-button [talk]="talk"></rate-button>
  `
})
...

生命周期(LIFECYCLE)

组件有一个定义良好的生命周期可以利用(onChange、onInit、onCheck、onAllChangesDone)。该TalkCmp组件不订阅任何生命周期事件,但一些其他组件可以。例如,该组件发生改变时会触发起change事件。

@Component({
  selector: 'cares-about-changes',
  properties: ['field1', 'field2'],
  lifecycle: [onChange]
})
class CareAboutChanges {
  field1;
  field2;
  onChange(changes) {
    //..
  }
}

注入(INJECTABLES)

一个组件可以包含一个注入对象列表,它的子组件可能也需要注入

@Component({
  selector: 'conf-app',
  viewInjector: [ConfAppBackend, Logger]
})
class TalksApp {
  //...
}
class TalksCmp {
  constructor(backend:ConfAppBackend) {
    //...
  }
}

在这个例子中,我们在根组件中声明ConfAppBackend,和Logger,这使得它们在整个应用程序可用,TalksCmp组件注入ConfAppBackend,我将在本文的第二部分详细讨论依赖注入

HOST元素(HOST ELEMENT)

要将Angular组件渲染成DOM中的某种东西,你需要在Angular组件中结合一个DOM元素,我们称这些叫host元素。

一个组件可以用以下方式于其host DOM元素进行交互

例如,组件可以使用host事件监听输入,对输入值进行处理及将其存储在一个字段中,angular会于DOM同步已存储的值

@Component({
  selector: 'trimmed-input',
  host: {
    '(input)': 'onChange($event.target.value)',
    '[value]': 'value'
  } 
})
class TrimmedInput {
  value: string;
  onChange(updatedValue: string) {
    this.value = updatedValue.trim();
  }
}

请注意,我真得直接与DOM交互。Angular2旨在提供一个更高层次的API,所以在原生平台(native platform),DOM,只会反映angular应用程序的状态。

这里几个原因非常有用:

有时候,你只需要直接与DOM进行交互。Angular 2提供了这样的API,但我们的希望是,你很少会需要使用它们。

组件是自描述性(COMPONENTS ARE SELF-DESCRIBING)

我所列出的组件构成.

在Angular2中所有的这些组件都具有自描述性,所以组件的注释(annotations)包含它们的实例所需要的所有信息。这是非常重要的。

这意味着任何组件可以引导作为应用程序,它并不需要任何特殊的方式,此外,任何组件可以被加载到一个router-outlet(ng-view)。因此,您可以编写一个应用程序组件,可被引导(bootstrap),加载路由(route),或直接用于其他组件,这将导致更少的API来学习。同时也让更多的组件可重用。

指令是什么?(WHAT ABOUT DIRECTIVES?)

如果您熟悉angular 1,你一定想知道“指令发生了什么变化”。

其实指令还在Angular 2中,组件只是指令中的最重要的一种,但不是唯一的指令类型,一个组件是一个指令,一个视图,你仍然可以编写一个没有视图的装饰器风格(decorator-style)的指令

![](http://www.kittencup.com/images/2015-07-17-Angular 2的核心概念/c.png)

依赖注入(DEPENDENCY INJECTION)

让我们来谈论Angular 的另一个重要基石,依赖注入。

依赖注入背后的想法很简单,如果有一个依赖于一个服务的组件。您无需自己创建服务并提供给组件,相反,你可以在构造函数中申请该服务,框架将会自动提供给你该服务,通过这样做,你可以依赖接口,而不是具体类型,这将导致更多的代码解耦,使可测试性和其他更好的事情。

![](http://www.kittencup.com/images/2015-07-17-Angular 2的核心概念/d.png)

Angular 2配备了依赖注入模块,看它如何被使用,让我们来看看下面的组件,该指令渲染一个talks的列表

@Component({selector: 'talk-list'})
@View({templateUrl: 'talks.html', directives: [NgFor]})
class TalkList {
  constructor() {
    //..get the data
  }
}
<!-- talks.html -->
<h2>Talks:</h2>
<div *ng-for="var t of talks">
  {{t.name}}
</div>

我们模拟一个简单的服务,会提供给我们数据

class TalksAppBackend {
  fetchTalks() {
    return [
      { name: 'Are we there yet?' },
      { name: 'The value of values' }
    ];
  }
}

我们如何使用这项服务?一种方法是在我们的组件创建该服务的一个实例。

class TalkList {
  constructor() {
    var backend = new TalksAppBackend();
    this.talks = backend.fetchTalks();
  }
}

这是一个不错的演示应用程序,但对于实际应用并不好,在实际应用中TalksAppBackend将不只是返回对象的数组,它会发出HTTP请求来获取数据,这意味着在单元测试中这个组件会创建真实的http-requrest(这不是一个好主意),这个问题是由已经耦合TalkList到TalksAppBackend这一事实引起的

我们可以通过注入一个实例TalksAppBackend到构造函数解决这个问题,所以我们可以把它在测试中轻易更换,就像这样:

class TalkList {
  constructor(backend:TalksAppBackend) {
    this.talks = backend.fetchTalks();
  }
}

这告诉了angular TalksList 依赖于TalksAppBackend,现在我们需要告诉Angular如果创建依赖,我们可以为此组件通过添加viewInjector属性

@Component({
  selector: 'talk-list',
  viewInjector: [TalksAppBackend]
})
class TalkList {
  constructor(backend:TalksAppBackend) {
   this.talks = backend.fetchTalks();
  }
}

该TalksAppBackend服务必须在TalkList组件或其祖先指定,所以,如果你喜欢写用Angular 1编写应用方式,你可以在根组件配置你的所有注射​​服务

@Component({
  selector: 'talk-app',
  viewInjector: [TalksAppBackend] // registered in the root component, so it can be injected into any component in the app.
})
class Application {
}
@Component({
  selector: 'talk-list'
})
class TalkList {
  constructor(backend:TalksAppBackend) {
   this.talks = backend.fetchTalks();
  }
}

(单一API)SINGLE API

Angular 1和Angular 2 都配备了依赖注入的模块,但在Angular 1,我们有几个API来注入依赖到指令:有的对象是按位置(例如,元素)注入,有的按名称,这有点混乱。Angular 2提供了注入依赖的单一的API,他们全部在组件的constrocutor中注入。

例如,此组件注入TalksAppBackend(这很可能是一个单列),和一个ElementRef,这是唯一的每个组件的一个实例。

class TalksList {
  constructor(elRef:ElementRef, backend:TalksAppBackend) {
  }
}

所以我们通过相同的API将全局和局部依赖注入到组件中。此外,组件可以使用相同API注入到其他组件中

class Component {
  constructor(sibling:SiblingCmp,
              @Parent parent:ParentCmp,
              @Ancestor ancestor:AncestorCmp) {
  }
}

依赖注入是你可能马上看不到的好处之一,但当你的应用程序增长更大时,它就越重要

(属性绑定)PROPERTY BINDINGS

Angular 使用属性绑定与组件树上的MODEL和DOM自动同步,要理解为什么这是很重要的,让我们来看看这个应用程序。

![](http://www.kittencup.com/images/2015-07-17-Angular 2的核心概念/e.png)

我们知道,这个应用程序将会有一个组件树。除了这棵树,它还将有一个模型。我们说这是简单的JavaScript对象,如下所示:

{
  filters: {
    speaker: "Rich Hickey",
  }
  talks: [
    {
      title: "Are we there yet?",
      speaker: "Rich Hickey",
      yourRating: null,
      avgRating: 9.0
    }
  ]
}

现在,想象一下一个事件改变model。我很喜欢它的talks,我给它9.9。

{
  filters: {
    speaker: "Rich Hickey",
  }
  talks: [
    {
      title: "Are we there yet?",
      speaker: "Rich Hickey",
      yourRating: null,
      avgRating: 9.9
    }
  ]
}

如果我必须找到所有可能有改变的地方并手动更新他们,那将是很繁琐且易出错,我想要应用程序自动来反映这一变化,这就是属性绑定。

在Angular虚拟机一轮结束,它会检查每个组件的组件树。更具体地说,它会检查每一个属性绑定(每一个方括号,每一对大括号),并将更新组件。它还将更新DOM来匹配组件树的状态。

ZONES

在Angular 1 你必须使用scope.$apply来告诉框架需要检查更新的内容,在Angular 2中你不必担心这个问题,Angular 2使用Zone.js知道当前检查是必须得,这意味着在使用第3方库集成时不需要在调用scope.$apply方法