为什么在 ngAfterContentInit() 中使用 setTimeOut,0?

Why is setTimeOut,0 used in ngAfterContentInit()?

添加在底部的这段代码的目的是响应 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,但我不明白为什么?

这可能是有原因的。 setTimeOut 是一个异步函数,当同步函数结束时执行。 前段时间使用了一个异步渲染组件的库,但是它有一个bug,把一个组件放在了右边。 所以我想用打字稿的样式来改变它,但这样做没有看到任何变化。为什么?因为我的代码是同步的,库将我的组件放在左边后放在右边。最简单的解决方案是将样式设置为 10 的 setTimeOut。它奏效了

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

之前的开发者添加 setTimeout 因为 A,各种 (Angular) 组件的行为之间可能存在竞争条件,或者 B,一个组件在一次更改中更改了另一个组件的状态检测周期(以错误表示)。

开发者实现的行为

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

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

YouTube 上有一个很棒的视频:What the heck is the event loop anyway?,我强烈建议您去看这个视频!

如果出现竞争条件

您的两个组件可能会相互竞争,为了演示,我们假设 JS 引擎“任务列表”如下所示:

  • 组件 A 做了一些事情
  • 组件 A 想在它的子组件中设置一些东西:组件 B
  • Angular 运行 一个 CD 周期并尝试呈现您的组件
  • 子组件 B 做一些事情

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

  • 组件 A 做了一些事情
  • Angular 运行 一个 CD 周期并尝试呈现您的组件
  • 子组件 B 做一些事情
  • 组件 A 想在它的子组件中设置一些东西:组件 B

这样你的 B 组件在创建时就会存在。

如果状态不一致

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

此时,你可以对自己说,太好了这个问题不会出现在产品中所以我很好你不好这个问题会导致性能变差 因为 Angular 会 运行 比它应该的更多的变化检测周期,因为它认为某些东西已经改变,而实际上你并不打算改变任何东西。所以你应该找出这个问题的原因并修复它。

官方 Angular 网站有关于此行为的 dedicated document page for this with a video guide on how to solve the problem. There is also a detailed Whosebug answer 以及为什么要检查它。

至于你原来的问题,通过添加 setTimeout 开发者技巧 Angular 来通过这个双重检查,因为 updateContentChildren 函数只会在当前变化检测之后执行已完成。这意味着您的内部状态始终比 UI“领先一步”,因为某些工作总是在 CD 周期结束后完成。