首页 > 解决方案 > 为什么在 ngAfterContentInit() 中使用 setTimeOut,0?

问题描述

在底部添加的这段代码的目的是对 ContentChildren 中的更改做出反应,ContentChildren 是链接到<td>元素的指令。它取自一本书,有以下行:

setTimeout(() => this.updateContentChildren(this.modelProperty), 0);

在我看来,这是一种特殊的结构。我不明白为什么作者添加了一个延迟为 0 的超时。相反,他可以只使用以下方法更新 ContentChildren:

this.updateContentChildren(this.modelProperty)

然而他没有这样做,我不明白为什么。有人能解释一下吗?

@Directive({
    selector: "table"
})
export class PaCellColorSwitcher {

    @Input("paCellDarkColor")
    modelProperty: Boolean;

    @ContentChildren(PaCellColor, {descendants: true})
    contentChildren: QueryList<PaCellColor>;

    ngOnChanges(changes: { [property: string]: SimpleChange }) {
        this.updateContentChildren(changes["modelProperty"].currentValue);
    }

    ngAfterContentInit() {
        this.contentChildren.changes.subscribe(() => {
            setTimeout(() => this.updateContentChildren(this.modelProperty), 0);
        });
    }

    private updateContentChildren(dark: Boolean) {
        if (this.contentChildren != null && dark != undefined) {
            this.contentChildren.forEach((child, index) => {
                child.setColor(index % 2 ? dark : !dark);
            });
        }
    }

更新:当我在没有setTimeOut(,0)函数的情况下运行它时,我得到一个ExpressionChangedAfterItHasBeenCheckedError,但我不明白为什么?

标签: angularlifecycle

解决方案


首先,我想说这是不好的做法,你应该避免它。但是我不会撒谎,我自己也使用过几次这个技巧。

之前的开发人员补充说setTimeout,因为 A,各种(Angular)组件的行为之间可能存在竞争条件,或者 B,一个组件在更改检测周期中更改另一个组件的状态(由错误指示)。

开发人员实现的行为

首先,我需要解释开发人员通过将他的函数设置setTimeout为零延迟来实现什么。Javascript 是一种单线程语言,这意味着默认情况下 JS 引擎一次只做一件事。

解释这一点的超级简化(并且有点错误)的方式是 JS 引擎有一个要在列表中执行的语句列表,它总是选择第一个。当您在 a 中设置某些内容时,setTimeout您将该语句(您的函数调用)放入此任务列表的末尾,因此当其他所有内容“排队”执行直到该点被处理之前,它将被执行。

YouTube 上有一个关于此的精彩视频:事件循环到底是什么?,我强烈建议你去看这个视频!

在比赛条件的情况下

可能会发生您的两个组件相互竞争的情况,以演示假设 JS 引擎“任务列表”如下所示:

  • 组件 A 做了一些事情
  • 组件 A 想在其子组件中设置一些东西:组件 B
  • Angular 运行 CD 循环并尝试渲染您的组件
  • 子组件 B 做了一些事情

这里的问题是,在第 2 步中,您的子组件 (B) 尚未创建,因此尝试使用它会引发错误。通过将修改组件 B 的语句放入setTimeout您的任务列表中,将如下所示:

  • 组件 A 做了一些事情
  • Angular 运行 CD 循环并尝试渲染您的组件
  • 子组件 B 做了一些事情
  • 组件 A 想在其子组件中设置一些东西:组件 B

这样,您的 B 组件将在创建时存在。

如果状态不一致

Angular 运行一个所谓的变更检测周期来检查应用程序状态发生了哪些变化以及 UI 需要如何更新。在开发人员模式下,此检查针对同一周期运行两次,并比较输出。如果存在差异,框架将抛出​​上述ExpressionChangedAfterItHasBeenCheckedError错误以警告您。

在这一点上,你可以对自己说,很好,这个问题不会出现在产品中,所以我很好,但你不好这个问题会导致性能恶化,因为 Angular 会运行比它应该运行的更多的变更检测周期,因为它认为某些事情发生了变化,而实际上您并不打算改变任何事情。所以你应该追踪这个问题的路径原因并修复它。

Angular 官方网站为此提供了一个专门的文档页面,其中包含有关如何解决问题的视频指南。这里还有一个关于此行为的详细 Stackoverflow 答案以及为什么要检查。

因此,对于您的原始问题,通过添加setTimeout开发人员技巧 Angular 以通过此双重检查,因为该updateContentChildren函数仅在当前更改检测完成后才会执行。这意味着您的内部状态总是比用户界面“提前一个刻度”,因为某些工作总是在 CD 周期完成后完成。


推荐阅读