【Angular】关于AsyncPipe你不知道的3件事!

原文地址:https://blog.thoughtram.io/angular/2017/02/27/three-things-you-didnt-know-about-the-async-pipe.html

你肯定听说过Angular的AsyncPipe对不对?它是一个非常方便的管道(Pipe),可以让我们在模版中使用,所以我们不需要去处理从Observables或者Promises中的数据。 结果就是,AsyncPipe有一些神奇的功能,可能不是那么明显。在这篇文章中,我们将会对这个有用的小工具的内部工作原理做一些阐述。

订阅到Observables

通常我们考虑使用AsyncPipe时,只是用它来解析从http调用中返回的数据。我们发出一个http调用,返回一个Observable<Response>对象,然后做一些转换(比如:map(...).filter(...)),最后将一个Observable暴露给我们组件的模板。这里是就是我们通常使用的时候的样子:

...
@Component({
  ...
  template: `
    <md-list>
      <a md-list-item
        *ngFor="let contact of contacts | async"
        title="View {{contact.name}} details">
        <img md-list-avatar [src]="contact.image" alt="Picture of {{contact.name}}">
        <h3 md-line>{{contact.name}}</h3>
      </a>
    </md-list>`,
})
export class ContactsListComponent implements OnInit {

  contacts: Observable<Array<Contact>>;

  constructor(private contactsService: ContactsService) {}

  ngOnInit () {
    this.contacts = this.contactsService.getContacts();
  }
}

在这种情况下,我们的Observable 就是我们所说的短命(原文short-lived)的。Observable 在这种情况下只发射一个值,就是一个contacts数组,并在发射之后结束。这是使用http的典型情况,它基本上是使用Promises的唯一场景。

然而,我们可以完全拥有发出多个值的Observables。想想使用websockets的例子。我们可能有一个随着时间的推移而构建的数组!我们来模拟一个可以发出数字数组的Observable。但是,不是一次只发出一个数组,它会在每次添加新项目时发出一个数组。为了不让数组无限增长,我们将其限制为最后五个项目。

...
@Component({
  selector: 'my-app',
  template: `
    <ul>
      <li *ngFor="let item of items | async">{{item}}</li>
    </ul>`
})
export class AppComponent {
  items = Observable.interval(100)
                    .scan((acc, cur)=>[cur, ...acc].slice(0, 5), []);             
}

注意我们的列表如何保持良好的同步,不用多说,感谢AsyncPipe!

跟踪引用

如果没有AsyncPipe的帮助,让我们回到上面的代码并重构上面的代码。但是,在这里我们使用一个按钮来重新生成数字,并且在每次重新生成这个序列的时候随机选择元素的背景色。

...
@Component({
  selector: 'my-app',
  template: `
    <button (click)="newSeq()">New random sequence</button>
    <ul>
      <li [style.background-color]="item.color"
          *ngFor="let item of items">{{item.num}}</li>
    </ul>`
})
export class AppComponent {
  items = [];

  constructor () {
    this.newSeq();
  }

  newSeq() {

    // generate a random color
    let color = '#' + Math.random().toString(16).slice(-6);

    Observable.interval(1000)
          .scan((acc, num)=>[{num, color }, ...acc].slice(0, 5), [])
          .subscribe(items => this.items = items);
  }
}

在线运行地址 运行这个例子,注意到什么了吗?随着按钮的每次点击,颜色会在越来越多的不同颜色之间来回滚动。那就是因为在这种情况下,Observable就是我们所说的长寿(原文:long-lived)的。 此外,每次我们点击按钮,我们都在创建另外一个这些长寿的Observable,而没有清理以前的。

让我们重构代码来跟踪订阅,并在每次创建一个新的Observable时,都会删除我们长寿的Observable。

...
export class AppComponent {
  ...
  subscription: Subscription;

  newSeq() {

    if (this.subscription) {
      this.subscription.unsubscribe();
    }

    // generate a random color
    let color = '#' + Math.random().toString(16).slice(-6);

    this.subscription = Observable.interval(1000)
          .scan((acc, num)=>[{num, color }, ...acc].slice(0, 5), [])
          .subscribe(items => this.items = items);
  }
}

在线运行地址 每次我们订阅一个Observable ,我们将它保存到组件实例的一个属性中。然后,当我们再次运行newSeq的时候,我们检查this.subscription是否存在,如果存在的话,我们就需要去调用unsubscribe来取消订阅。这就是为什么我们看不到我们的列表在各种颜色之间跳转,不管我们点击了按钮多少次。

现在我们再次看下AsyncPipe。让我们来改变ngFor来应用AsyncPipe。

@Component({
  selector: 'my-app',
  template: `
    <button (click)="newSeq()">New random sequence</button>
    <ul>
      <li [style.background-color]="item.color"
          *ngFor="let item of items | async">{{item.num}}</li>
    </ul>`
})
export class AppComponent {
  items: Observable<any>;

  constructor () {
    this.newSeq();
  }

  newSeq() {

    // generate a random color
    let color = '#' + Math.random().toString(16).slice(-6);

    this.items = Observable.interval(1000)
                           .scan((acc, num)=>[{num, color }, ...acc].slice(0, 5), []);
  }
}

在线运行地址 我确信你已经听说AsyncPipe一旦组件被销毁,就会从Observables取消订阅。但是,一旦表达式的引用发生变化,你也知道它取消订阅吗?没错,只要我们为此分配一个新的Observable,则AsyncPipe将自动取消订阅先前绑定的Observable!这不仅使我们的代码变得美观干净,还能保护我们免受非常微妙的内存泄漏。

标记要检查的东西

好的。 我们为您提供最后一个漂亮的AsyncPipe功能!如果你已经阅读了我们的关于Angular变更检测的文章,你应该知道,你可以使用OnPush策略来进一步加快变更检测过程。我们重构我们的例子,并引入一个SeqComponent来显示序列,而我们的根组件将管理数据并通过输入绑定传递它。

我们开始创建一个非常简单的SeqComponent。

@Component({
  selector: 'my-seq',
  template: `
    <ul>
      <li [style.background-color]="item.color" 
          *ngFor="let item of items">{{item.num}}</li>
    </ul>`
})
export class SeqComponent {
  @Input()
  items: Array<any>;
}

请注意@Input()装饰器items属性,这意味着组件将通过属性绑定从外部接收。 我们的根组件维护一个数组seqs,并通过点击一个按钮将新的长寿的Observables推入它。 它使用* ngFor将这些Observables中的每一个传递给一个新的SeqComponent实例。还要注意,我们在我们的属性绑定表达式([items] =“seq | async”)中使用AsyncPipe来传递纯数组而不是Observable,因为这是SeqComponent所期望的。

@Component({
  selector: 'my-app',
  template: `
    <button (click)="newSeq()">New random sequence</button>
    <ul>
      <my-seq *ngFor="let seq of seqs" [items]="seq | async"></my-seq>
    </ul>`
})
export class AppComponent {
  seqs = [];
  
  constructor () {
    this.newSeq();
  }
  
  newSeq() {
    
    // generate a random color
    let color = '#' + Math.random().toString(16).slice(-6);
    
    this.seqs.push(Observable.interval(1000)
                           .scan((acc, num)=>[{num, color }, ...acc].slice(0, 5), []));
  }
}

到目前为止,我们还没有对潜在的变更检测策略做出任何改变。 如果您点击按钮几次,请注意我们如何获取多个列表,以不同的时间独立更新。 在线运行地址 然而,在变更检测方面,这意味着每次Observables 都可以检查所有组件。这是浪费资源。通过将SeqComponent的更改检测设置为OnPush,我们可以做得更好,这意味着如果输入(我们的案例中的数组)发生变化,它将只检查它的绑定。

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'my-seq',
  ...
})

这非常有效。但是这里有个问题:它只在这种情况下起作用,因为我们的Observable 每次都是创建一个新的数组和值。即使这实际上并不太糟糕,实际上在大多数情况下都是有益的,让我们考虑一下我们使用一个不同的实现来修改现有的数组,而不是每次重新创建它。

Observable.interval(1000)
          .scan((acc, num)=>{
            acc.splice(0, 0, {num, color});
            if (acc.length > 5) {
              acc.pop()
            }
            return acc;
          }, [])

如果我们尝试,OnPush似乎不再工作,因为项目的引用根本不会改变。 实际上,当我们尝试这样做时,我们看到每个列表都不会超出其第一个元素。 在线运行地址

再次认识AsyncPipe! 我们来改变我们的SeqComponent,所以它需要一个Observable而不是一个数组作为它的输入。

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'my-seq',
  template: `
    <ul>
      <li [style.background-color]="item.color" 
          *ngFor="let item of items | async">{{item.num}}</li>
    </ul>`
})
export class SeqComponent {
  @Input()
  items: Observable<Array<any>>;
}

另请注意,它现在将AsyncPipe应用于其模板,因为它不再处理一个简单的数组。 我们的AppComponent还需要更改,以便不再在属性绑定中应用AsyncPipe。

<ul>
  <my-seq *ngFor="let seq of seqs" [items]="seq"></my-seq>
</ul>

在线运行地址

cool!现在似乎已经开始有效了。

让我们回顾一下,我们的数组实例不会改变,我们的Observable的实例也不会改变。那么为什么OnPush在这种情况下起作用了? 原因可以在AsyncPipe本身的源代码中找到

private _updateLatestValue(async: any, value: Object): void {
  if (async === this._obj) {
    this._latestValue = value;
    this._ref.markForCheck();
  }
}

AsyncPipe标记要检查的组件的ChangeDetectorRef,有效地告诉变更检测,该组件可能会发生更改。如果您想更详细地了解我们如何工作,我们建议您阅读我们深入的变更检测文章

总结

我们使用AsyncPipe作为一个漂亮的小工具来在我们的组件中保存几行代码。 实际上,它隐藏了来自我们的管理异步任务的很多复杂性。

转载于:https://my.oschina.net/zhongzhong5/blog/897372

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值