[译] 你真的晓得 Angular 单向数据流吗

原文链接:
Do you really know what unidirectional data flow means in Angular

关于单向数据流,还能够参考这篇文章,且文中另有
youtube 视频剖析:
Angular – What is Unidirectional Data Flow? Learn How the Angular Development Mode Works, why it’s important to use it and how to Troubleshoot it 。单向数据流一句话诠释就是:不要在 Angular 运用 Model 天生 View 这个历程当中再去修正 Model。

《[译] 你真的晓得 Angular 单向数据流吗》

大多数架构形式是很难邃晓的,尤其是在相干材料很少时那就越发头疼,比方 Angular 的单向数据流(unidirectional data flow)文档材料就很少,纵然官方文档上,也仅仅在 表达式指南模板表达式 两小块中略有说起。我也很少在网上搜到比较好的诠释文章,所以写此文是为了给 单向数据流(unidirectional data flow) 有个细致的诠释。

双向数据流和单向数据流

在议论 AngularJS 和 Angular 机能异同点时,就经常会提到单向数据流形式,它也是 Angular 比 AngularJS 机能更快的缘由地点。让我们一同看看单向数据流终究咋回事吧!(译者注:AngularJS 指的是 1.X 版本,2+ 版本叫 Angular,运用 TypeScript 言语重写了框架,包含架构形式举行了从新设想,但二者形似也神似。)

AngularJS 和 Angular 都是经由历程绑定来完成组件间数据通讯,比方在 AngularJS 定义一个父组件 A

app.component('aComponent', {
  controller: class ParentComponent() {
    this.value = {name: 'initial'};
  },
  template: `
    <b-component obj="$ctrl.value"></b-component>
  `
});
----------------
app.component('bComponent', {
    bindings: {
        obj: '='
    },

能够看到父组件 A 有个子组件 B,而且经由历程输入绑定把父组件 Avalue 属性值传给子组件 Bobj 属性:

<b-component obj="$ctrl.value"></b-component>

这和 Angular 中组件间通讯很相似了啊:

@Component({
    template: `
        <b-component [obj]="value"></b-component>
    ...
export class AppComponent {
    value = {name: 'initial'};
}
----------------
export class BComponent {
    @Input() obj;

第一件重要的事变是,Angular 和 AngularJS 都是在变动检测(change detection)时期才去更新绑定值。所以,当 Angular 框架在为父组件 A 运转变动检测时,它会更新子组件 Bobj 属性:

bComponentInstance.obj = aComponentInstance.value;

上面历程证明了单向数据流是数据从上往下流(译者注:即数据从组件树的父组件往子组件流),然则 AngularJS 却不一样,它能够许可在子组件中更新父组件的 value 属性值

app.component('parentComponent', {
  controller: function ParentComponent($timeout) {    
    $timeout(()=>{
      console.log(this.value); // logs {name: 'updated'}
    }, 3000)
  }
----------------
  
app.component('childComponent', {
    controller: function ChildComponent($timeout) {      
      $timeout(()=>{
        this.obj = { name: 'updated' };  
      }, 2000)

上面代码你能够看到两个 timeout 回调,第一个回调会更新子组件属性,第二个回调会耽误第一个回调一秒,搜检父组件属性是不是被更新。假如你在 AngularJS 中运转上面代码你会发明父组件属性已被更新了。让我们一同看看终究发作了什么?

当第一个回调运转时,子组件 Bobj 属性被更新为 {name: 'updated'},然后 AngularJS 运转变动检测。在变动检测历程当中,AngularJS 检测到子组件绑定属性的值发作转变,它会更新父组件 Avalue 属性值。这是 AngularJS 变动检测的内置功用。假如你在 Angular 中做一样的事变,它仅仅更新子组件的属性值,然则子组件的转变不会冒泡到父组件,即 Angular 不会转变父组件的属性值。这是升级版后的 Angular 变动检测完成,相较于 AngularJS 的最重大区分。但是,它却搅扰了我良久啊。

但是,Angular 也一样能够经由历程子组件来更新父组件,这个机制就是输出绑定(output binding)。能够像如许运用输出绑定:

@Component({
    template: `
        <h1>Hello {{value.name}}</h1>
        <a-comp (updateObj)="value = $event"></a-comp>
    ...
export class AppComponent {
    value = {name: 'initial'};
    
    constructor() {
        setTimeout(() => {
            console.log(this.value); // logs {name: 'updated'}
        }, 3000);
----------------
@Component({...})
export class AComponent {
    @Output() updateObj = new EventEmitter();
    
    constructor() {
        setTimeout(() => {
            this.updateObj.emit({name: 'updated'});
        }, 2000);

我认可这个和从子组件直接更新还不太一样,然则父组件值的确是更新了啊。很长一段时间我不邃晓为什么这没有被看作双向数据绑定?毕竟,数据通讯是在两个方向举行的。

直到有一晚我读到了 Two Phases of Angular Applications by Victor Savkin,他诠释道(译者注:为清楚邃晓,这个诠释不翻译):

Angular 2 separates updating the application model and reflecting the state of the model in the view into two distinct phases. The developer is responsible for updating the application model. Angular, by means of change detection, is responsible for reflecting the state of the model in the view.

(译者注:意义就是 Angular 划分了
更新顺序 model 在前 和
同步 model 和 view 在后两个步骤,开发者只需要关注
更新顺序 model
同步 model 和 view 由 Angular 框架担任,这个同步历程就是
变动检测,说白了就是
更新 view。)

(译者注:比方朋侪圈点赞,起首就是手指触发异步事宜来
更新顺序 model,如在 Post 组件里给 likes 属性加 1,然后鄙人一个
VM turn 时 Angular 会自动更新视图中绑定的 likes 值, 即
同步 model 和 view,点赞数就会从 0 变成 1。)

这让我消费好些天赋邃晓,所谓的运用输出绑定机制来更新父组件并非在变动检测中实行的:

<a-comp (updateObj)="value = $event"></a-comp>

相反,它是在变动检测前的更新顺序 model 阶段实行的。所以,单向数据流的意义是指在变动检测时期属性绑定变动的架构。在上面 Angular 代码示例中,不像 AngularJS,在 Angular 变动检测时期,并没有代码去让子组件 AComponent 去更新父组件 AppComponent 的属性。相反,输出绑定历程并没有在变动检测时期内运转,所以它没有把单向数据流转变成双向数据流。

别的,只管 Angular 没有内置机制能够使得在变动检测时期去更新父组件,但是能够经由历程同享效劳或播送同步事宜做到这一点。然则,由于 Angular 框架强制单向数据流,所以这么做会致使ExpressionChangedAfterItHasBeenCheckedError 毛病。想要相识致使这个毛病的缘由和解决方案能够参考 Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError

视图和效劳层的单向数据流

你能够晓得,大多数 web 顺序会设想成有两层:视图层和效劳层。

《[译] 你真的晓得 Angular 单向数据流吗》

Web 环境下视图层重要经由历程 DOM 构造向用户展现相干顺序数据,在 Angular 中这一层是经由历程组件做的。效劳层重如果处置惩罚和保留数据,正如上图中展现的,这一层又被切分为状况治理和一些基本部份,如 rest 效劳或可重用东西效劳(helpers)。

上文中提到的单向数据流指的是视图层,由于它说的是组件,而组件是用来构建视图的:

《[译] 你真的晓得 Angular 单向数据流吗》

但是,在跟着完成了 redux 架构的 ngrx 引入后,这又让人疑惑了,由于 redux 文档上有 这么一句(译者注:为清楚邃晓,这句不翻译):

Redux architecture revolves around a 
strict unidirectional data flow.

This means that all data in an application follows the same lifecycle pattern, making the logic of your app more predictable and easier to understand…

实际上,这个严厉单项数据流(strict unidirectional data flow)实在说的是效劳层而不是视图层。但我有时会搞混效劳层和视图层,会把 redux 的架构形式与 Angular 的构造形式联系起来,固然要防止搞混这两层嗷。Redux 说的单向数据流说的是效劳层,而不是视图层嗷。 Redux 重要说的是状况治理模块,会把我们上文说的双向数据流转变成单向数据流。
把双向数据流

《[译] 你真的晓得 Angular 单向数据流吗》

转换为单向数据流:

《[译] 你真的晓得 Angular 单向数据流吗》

    原文作者:lx1036
    原文地址: https://segmentfault.com/a/1190000013965691
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞