1. Angular 错误:ExpressionChangedAfterItHasBeenCheckedError

最近在做项目的时候发现一个错误:ExpressionChangedAfterItHasBeenCheckedError。

经过Google后了解到,这个是只有dev环境下才会报的错误,跟变更检测有关。

大意是:angular 在render页面时某个属性已赋值,然而在render结束后 AfterViewChecked 的时候属性值却发生了变化,这可能导致给用户显示的信息是错误的,所以爆出该错误。

1.1. 情景再现:

template:
父组件 aComponent 内循环输出子组件 bComponent。

<div *ngfor(let item of list; index of i)>
    <bComponent [img]="imgData[i]"></bComponent>
</div>

aComponent.ts

ngAfterViewInit() {
    异步获取list->then(()=>{
      获取imgData[];
    )
}

bComponent.ts

@Input() imgData;
images: string[] = [];

ngAfterViewInit() {
    this.images = imgData;
}

在我加了一些log后发现执行顺序:

aComponent ngOnInit()
aComponent ngAfterViewInit()

bComponent ngOnInit()
bComponent ngAfterViewInit()

因为刚开始异步获取list还没得到结果,所以父组件 ngOnInit-> ngAfterViewInit;

等到获得到list, 页面才会循环渲染输出子组件,执行其生命周期。

因为我将 this.images = imgData; 操作放在了ngAfterViewInit()生命周期里,
根据组件变更检测的执行顺序(digest cycle):

更新所有子组件/指令的绑定属性
调用所有子组件/指令的三个生命周期钩子:ngOnInit,OnChanges,ngDoCheck
更新当前组件的 DOM
为子组件执行变更检测(译者注:在子组件上重复上面三个步骤,依次递归下去)
为所有子组件/指令调用当前组件的 ngAfterViewInit 生命周期钩子

当更新 DOM 时,images还是空[],然后在 ngAfterViewInit() 被修改。

如果处于开发者模式,Angular 还会执行上面的 digest cycle 循环核查。

angular 在第二次 digest cycle 的时候发现DOM值和输入属性值不同,于是就报错了。

1.2. 解决:

this.images = imgData; 赋值操作 放到 ngOnInit() 内即可。

总结:了解组件的生命周期和执行过程还是很重要的,一不小心就可能掉坑里。

1.3. 参考:

[译] 关于 ExpressionChangedAfterItHasBeenCheckedError 错误你所需要知道的事情

Template-Driven-動態加入驗證屬性注意事項

评论

此博客中的热门博文