【问题标题】:What's the difference between markForCheck() and detectChanges()markForCheck() 和 detectChanges() 有什么区别
【发布时间】:2017-05-12 20:59:36
【问题描述】:

ChangeDetectorRef.markForCheck()ChangeDetectorRef.detectChanges() 有什么区别?

我只知道found information on SONgZone.run() 的区别,而不是这两个功能之间的区别。

对于仅参考文档的答案,请举例说明一些实际场景以供选择。

【问题讨论】:

    标签: angular angular2-changedetection


    【解决方案1】:

    detectChanges() : 无效

    Checks the change detector and its children.

    这意味着,如果你的模型(你的类)中的任何东西发生了变化,但它没有反映视图,你可能需要通知 Angular 来检测这些变化(检测本地变化)并更新查看。

    可能的情况可能是:

    1- 变化检测器从视图中分离出来(见detach

    2- 发生了更新,但尚未在 Angular 区域内,因此 Angular 不知道它。

    就像第三方函数更新了您的模型,而您想在此之后更新视图。

     someFunctionThatIsRunByAThirdPartyCode(){
         yourModel.text = "new text";
     }
    

    因为这段代码(可能)在 Angular 的区域之外,您很可能需要确保检测到更改并更新视图,因此:

     myFunction(){
       someFunctionThatIsRunByAThirdPartyCode();
    
       // Let's detect the changes that above function made to the model which Angular is not aware of.
        this.cd.detectChanges();
     }
    

    注意

    还有其他方法可以使上述工作,换句话说,还有其他方法可以将更改带入 Angular 更改周期。

    ** 您可以将该第三方函数包装在 zone.run 中:

     myFunction(){
       this.zone.run(this.someFunctionThatIsRunByAThirdPartyCode);
     }
    

    ** 您可以将函数包装在 setTimeout 中:

    myFunction(){
       setTimeout(this.someFunctionThatIsRunByAThirdPartyCode,0);
     }
    

    3- 在某些情况下,您在 change detection cycle 完成后更新模型,在这些情况下您会遇到这个可怕的错误:

    “检查后表达式发生了变化”;

    这通常意味着(来自 Angular2 语言):

    我看到您的模型发生了变化,这是由我接受的一种方式(事件、XHR 请求、setTimeout 和...)引起的,然后我运行了更改检测来更新您的视图并完成了它,但是随后您的代码中有另一个函数再次更新了模型,我不想再次运行更改检测,因为不再像 AngularJS 那样进行脏检查:D,我们应该使用单向数据流!

    你肯定会遇到这个错误:P。

    修复它的几种方法:

    1- 正确方式:确保更新在变更检测周期内(Angular2 更新是一种方式流程,只会发生一次,之后不要更新模型并将代码移动到更好的地点/时间)。

    2- 懒惰的方式:在更新之后运行 detectChanges() 让 angular2 开心,这绝对不是最好的方式,但是当你问可能的场景是什么时,这是其中之一.

    您的意思是:我真诚地知道您运行了更改检测,但我希望您再次执行此操作,因为在您完成检查后我必须即时更新某些内容。

    3- 将代码放在setTimeout 中,因为setTimeout 由区域修补,完成后将运行detectChanges


    来自文档

    markForCheck() : void
    

    Marks all ChangeDetectionStrategy ancestors as to be checked.

    当您的组件的 ChangeDetectionStrategyOnPush 时,最需要这样做。

    OnPush 本身的意思是,仅在发生任何这些情况时才运行更改检测:

    1- 组件的其中一个@inputs 已完全替换为新值,或者简单地说,如果@Input 属性的引用已完全更改。

    因此,如果您的组件的 ChangeDetectionStrategyOnPush,那么您有:

       var obj = {
         name:'Milad'
       };
    

    然后你像这样更新/变异它:

      obj.name = "a new name";
    

    这不会更新 obj 引用,因此不会运行更改检测,因此视图不会反映更新/突变。

    在这种情况下,您必须手动告诉 Angular 检查和更新视图(markForCheck);

    如果你这样做了:

      obj.name = "a new name";
    

    你需要这样做:

      this.cd.markForCheck();
    

    相反,下面会导致运行更改检测:

        obj = {
          name:"a new name"
        };
    

    完全用新的{}替换了之前的obj;

    2- 触发了一个事件,例如点击或类似的事情,或者任何子组件发出了一个事件。

    类似的事件:

    • 点击
    • 键位
    • 订阅事件

    简而言之:

    • 当您在 Angular 运行更改检测后更新模型时使用 detectChanges(),或者如果更新根本不在 Angular 世界中。

    • 如果您正在使用 OnPush 并且您通过改变一些数据来绕过 ChangeDetectionStrategy,或者您已经在 setTimeout 中更新了模型,请使用 markForCheck()

    【讨论】:

    • 所以如果你改变那个 obj ,视图不会被更新,即使你运行 detectChanges,也不会工作,因为没有任何变化 -这不是真的。 detectChanges 更新视图。见this in-depth explanation
    • 关于结论中的markForCheck,也不准确。这是来自this questionmodified example,它不会使用 OnPush 和 markForCheck 检测对象更改。但是the same example will work 如果没有 OnPush 策略。
    • @Maximus,关于您的第一条评论,我阅读了您的帖子,感谢您的评论。但是在您的解释中,您是说如果策略是 OnPush ,则意味着如果 this.cdMode === ChangeDetectorStatus.Checked 它不会更新视图,这就是您使用 markForCheck 的原因。
    • 关于你到 plunker 的链接,这两个例子对我来说都很好,我不知道你的意思
    • 非常棒的答案,谢谢!我学到了几件事。 @Milad
    【解决方案2】:

    我制作了一个 4 分钟的截屏视频来解释 ma​​rkForCheck()detectChanges() 之间的区别 - https://www.youtube.com/watch?v=OcphK_aEd7I

    【讨论】:

      【解决方案3】:

      cd.detectChanges() 将立即运行从当前组件到其后代的更改检测。

      cd.markForCheck() 不会运行变更检测,而是将其祖先标记为需要运行变更检测。下次更改检测在任何地方运行时,它也会对那些被标记的组件运行。

      • 如果您想减少调用更改检测的次数,请使用cd.markForCheck()。通常,更改会影响多个组件,并且会在某个地方调用更改检测。您实际上是在说:让我们确保该组件在发生这种情况时更新。 (视图会在我编写的每个项目中立即更新,但不是在每个单元测试中)。
      • 如果您不能确定cd.detectChanges() 不是当前 正在运行更改检测,请使用cd.markForCheck()detectChanges() 在这种情况下会出错。这可能意味着您尝试编辑祖先组件的状态,这与 Angular 的变更检测设计的假设背道而驰。
      • 如果视图在其他操作之前同步更新很重要,请使用detectChanges()markForCheck() 实际上可能不会及时更新您的视图。单元测试会影响您的视图,例如,可能需要您手动调用 fixture.detectChanges(),而在应用本身中不需要这样做。
      • 如果您要更改祖先多于后代的组件中的状态,则使用 detectChanges() 可能会提高性能,因为您不必在组件的祖先上运行更改检测。

      【讨论】:

        【解决方案4】:

        两者最大的区别在于detectChanges()实际触发变更检测,而markForCheck()不触发变更检测。

        检测变化

        这用于对组件树运行更改检测,从您触发detectChanges() 的组件开始。因此更改检测将针对当前组件及其所有子组件运行。 Angular 在ApplicationRef 中保存对根组件树的引用,当发生任何异步操作时,它会通过包装方法tick() 触发对该根组件的更改检测:

        @Injectable()
        export class ApplicationRef_ extends ApplicationRef {
          ...
          tick(): void {
            if (this._runningTick) {
              throw new Error('ApplicationRef.tick is called recursively');
            }
        
            const scope = ApplicationRef_._tickScope();
            try {
              this._runningTick = true;
              this._views.forEach((view) => view.detectChanges()); <------------------
        

        view 这里是根组件视图。正如我在What are the implications of bootstrapping multiple components 中描述的那样,可以有许多根组件。

        @milad 描述了您可能需要手动触发变更检测的原因。

        markForCheck

        正如我所说,这家伙根本不会触发变更检测。它只是从当前组件向上移动到根组件并将其视图状态更新为ChecksEnabled。以下是源代码:

        export function markParentViewsForCheck(view: ViewData) {
          let currView: ViewData|null = view;
          while (currView) {
            if (currView.def.flags & ViewFlags.OnPush) {
              currView.state |= ViewState.ChecksEnabled;  <-----------------
            }
            currView = currView.viewContainerParent || currView.parent;
          }
        }
        

        组件的实际更改检测没有计划,但当它在未来发生时(作为当前或下一个 CD 周期的一部分),即使父组件视图具有分离的更改检测器,也会检查它们。可以使用cd.detach() 或指定OnPush 更改检测策略来分离更改检测器。所有原生事件处理程序都会标记所有父组件视图以供检查。

        这种方法经常用在ngDoCheck 生命周期钩子中。你可以在If you think ngDoCheck means your component is being checked — read this article阅读更多内容。

        有关详细信息,另请参阅Everything you need to know about change detection in Angular

        【讨论】:

        • 为什么 detectChanges 对组件及其子组件起作用,而 markForCheck 对组件和祖先起作用?
        • @pablo,这是设计使然。我不太了解其中的原理
        • @jerry,推荐的方法是使用异步管道,它在内部跟踪订阅并在每个新值上触发 markForCheck。因此,如果您不使用异步管道,那可能就是您应该使用的。但是,请记住,存储更新应该是一些异步事件的结果,以便启动更改检测。大多数情况下都是如此。但也有例外blog.angularindepth.com/…
        • @MaxKoretskyiakaWizard 感谢您的回复。是的,商店更新主要是 fetch 或设置 isFetching before 的结果。并且在 fetch 之后.. 但我们不能总是使用 async pipe 因为在订阅中我们通常有一些事情要做,比如 call setFromValues do some comparison.. 如果 async 本身调用 markForCheck 如果有什么问题我们自己叫它?但同样,我们通常在 ngOnInit 中有 2-3 个或有时更多选择器获取不同的数据......我们在所有这些选择器中调用 markForCheck.. 可以吗?
        • 在那种情况下,我很困惑@MaxKoretskyi :(我阅读了你所有的教程并看到了你的演讲,我仍然无法理解这个:为什么当我调用 markForCheck() 时会触发更改检测? 我知道它将组件及其前身标记为脏到根组件,并且将在“当前或下一个 CD 周期期间”进行检查。但是谁触发了下一个 CD 周期?我也尝试像调试它一样你做了,但没有用。还有其他方法吗?stackoverflow.com/questions/64157710/…
        猜你喜欢
        • 2019-12-14
        • 2010-10-02
        • 2011-12-12
        • 2010-09-16
        • 2012-03-14
        • 2012-02-06
        • 2011-02-25
        • 2011-11-22
        • 2015-03-26
        相关资源
        最近更新 更多