双向 Angular 绑定中的 ExpressionChangedAfterItHasBeenCheckedError

ExpressionChangedAfterItHasBeenCheckedError in two-way Angular binding

这是一个example

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h1>{{ foo }}</h1>    
      <bpp [(foo)]="foo"></bpp>
    </div>
  `,
})
export class App {
  foo;
}

@Component({
  selector: 'bpp',
  template: `
    <div>
      <h2>{{ foo }}</h2>
    </div>
  `,
})
export class Bpp {
  @Input('foo') foo;

  @Output('fooChange') fooChange = new EventEmitter();

  ngAfterViewInit() {
    const potentiallyButNotNecessarilyAsyncObservable = Observable.of(null);

    potentiallyButNotNecessarilyAsyncObservable.subscribe(() => {
      this.fooChange.emit('foo');
    })
  }
}

偶尔会出现错误的地方:

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'foo'

这是因为双向绑定是由一个可以在同一刻度上获取值的可观察对象更新的。我宁愿不要用 setTimeout 包装上面的逻辑,因为它看起来像 hack 并且使控制流复杂化

如何避免此处出现此错误?

ExpressionChangedAfterItHasBeenCheckedError 错误是否有不良影响或可以忽略?如果可以的话,change detector能不能静默一下,不污染控制台?

让我们首先解开双向数据绑定以简化解释:

<div>
  <h1>{{ foo }}</h1>    
  <bpp [foo]="foo" (fooChange)="foo=$event"></bpp>
</div>

还是一样的效果,偶尔会报错。仅当 potentiallyButNotNecessarilyAsyncObservable 是同步的时才会产生错误。所以我们也可以这样替换:

ngAfterViewInit() {
    const potentiallyButNotNecessarilyAsyncObservable = Observable.of(null);

    potentiallyButNotNecessarilyAsyncObservable.subscribe(() => {
      this.fooChange.emit('foo');
    })

有了这个:

ngAfterViewInit() {
    this.fooChange.emit('foo');

此案例属于 Synchronous event broadcasting 文章中解释的错误类别 Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error

ngAfterViewInit 生命周期挂钩在处理父组件更改后触发。 Everything you need to know about change detection in Angular 中解释了与子组件相关的挂钩顺序。现在 Angular 记得当它 运行 更改 App 组件的检测时 foo 的值是 undefined,但在验证阶段该值是 foo 由子 Bpp 组件更新。因此它会产生错误。

What can be done to avoid this error here?

我链接的文章中描述了修复和问题。如果您不想重新设计逻辑,这里唯一安全的选择是异步更新。您还可以 运行 对父组件进行更改检测,但它们可能会导致无限循环,因为对组件的更改检测会触发对组件子组件的更改检测。

Does ExpressionChangedAfterItHasBeenCheckedError error have ill effects or can it be ignored?

不良影响是应用程序 App.foo==='foo' 和视图 {{foo}}===undefined 中的状态不一致,直到下一次 digest cycle 迭代。该错误在开发模式下无法关闭,但在生产模式下不会出现。

Two Phases of Angular Applications 也很好地解释了这个错误的心智模型。