markForCheck()和detectChanges()之间有什么区别

par*_*ent 146 angular2-changedetection angular

ChangeDetectorRef.markForCheck()和之间有什么区别ChangeDetectorRef.detectChanges()

我只在SO上找到了关于NgZone.run()这两个函数之间的差异的信息,但不是这两个函数之间的区别.

对于仅提及文档的答案,请说明一些实际场景,以选择其中一个.

Mil*_*lad 195

来自docs:

detectChanges():void

检查变化探测器及其子.

这意味着,如果存在模型(您的类)中的任何内容发生更改但未反映视图的情况,您可能需要通知Angular检测这些更改(检测本地更改)并更新视图.

可能的情况可能是:

1-更换探测器与视图分离(参见分离)

2-更新已经发生,但它没有进入Angular Zone,因此,Angular不知道它.

就像第三方功能更新了您的模型并且您希望在此之后更新视图一样.

 someFunctionThatIsRunByAThirdPartyCode(){
     yourModel.text = "new text";
 }
Run Code Online (Sandbox Code Playgroud)

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

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

   // Let's detect the changes that above function made to the model which Angular is not aware of.
    this.cd.detectChanges();
 }
Run Code Online (Sandbox Code Playgroud)

注意:

还有其他方法可以完成上述工作,换句话说,还有其他方法可以将这种变化带入Angular变更周期.

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

 myFunction(){
   this.zone.run(this.someFunctionThatIsRunByAThirdPartyCode);
 }
Run Code Online (Sandbox Code Playgroud)

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

myFunction(){
   setTimeout(this.someFunctionThatIsRunByAThirdPartyCode,0);
 }
Run Code Online (Sandbox Code Playgroud)

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

"检查后表情发生了变化";

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

我看到你的模型中的一个更改是由我接受的方式之一(事件,XHR请求,setTimeout和......)然后我运行我的更改检测来更新你的视图我完成了它,但是那时还有另一个在您的代码中再次更新模型的功能,我不想再次运行我的更改检测,因为不再像AngularJS那样进行脏检查:D我们应该使用单向数据流!

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

几种方法来解决它:

1- 正确的方法:确保更新在更改检测周期内(Angular2更新是发生一次的单向流,不要在此之后更新模型并将代码移动到更好的位置/时间).

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

这样你就说:我真诚地知道你运行了变更检测,但是我希望你再次这样做,因为我必须在完成检查后立即更新.

3-将代码放入a中setTimeout,因为它setTimeout是按区域修补的,并detectChanges在完成后运行.


来自文档

markForCheck() : void
Run Code Online (Sandbox Code Playgroud)

标记所有ChangeDetectionStrategy祖先以进行检查.

这主要是在需要时ChangeDetectionStrategy你的组件是OnPush.

OnPush本身意味着,只有在发生以下任何情况时才运行更改检测:

1-如果@Input属性的引用完全改变,则组件的一个@inputs已完全替换为新值,或者简单地放置.

所以,如果ChangeDetectionStrategy您的组件是OnPush,然后你必须:

   var obj = {
     name:'Milad'
   };
Run Code Online (Sandbox Code Playgroud)

然后你更新/改变它像:

  obj.name = "a new name";
Run Code Online (Sandbox Code Playgroud)

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

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

所以,如果你这样做:

  obj.name = "a new name";
Run Code Online (Sandbox Code Playgroud)

你需要这样做:

  this.cd.markForCheck();
Run Code Online (Sandbox Code Playgroud)

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

    obj = {
      name:"a new name"
    };
Run Code Online (Sandbox Code Playgroud)

这完全取代了以前的obj用新的{};

2-事件已触发,如点击或某些事情或任何子组件发出事件.

事件如:

  • 点击
  • KEYUP
  • 订阅活动
  • 等等

简而言之:

  • detectChanges()在角度运行变更检测后更新模型时使用,或者如果更新根本没有处于角度世界中.

  • 使用markForCheck()如果您使用的OnPush和你绕过ChangeDetectionStrategy通过突变的一些数据,或者你已经更新了内部模型的setTimeout ;

  • _因此,如果你改变了obj,那么视图就不会被更新,即使你运行了detectChanges,也不会有效,因为没有任何改变_ - 这不是真的.`detectChanges`更新视图.请参阅[此深入解释](https://hackernoon.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f). (4认同)
  • 非常好的答案,谢谢!我学到了几件事。@米拉德 (3认同)

Max*_*kyi 85

两者之间的最大区别在于detectChanges()实际触发变化检测,而markForCheck()不会触发变化检测.

detectChanges

这个用于从您触发的组件开始的组件树运行更改检测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()); <------------------
Run Code Online (Sandbox Code Playgroud)

view这是根组件视图.正如我在引导多个组件的含义中描述的那样,可以有许多根组件.

@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;
  }
}
Run Code Online (Sandbox Code Playgroud)

组件的实际更改检测未进行调度,但将在未来发生时(作为当前或下一个CD周期的一部分),即使它们具有分离的更改检测器,也将检查父组件视图.可以通过使用cd.detach()或通过指定OnPush变化检测策略来分离变化检测器.所有本机事件处理程序都标记所有父组件视图以进行检查.

这种方法通常用于ngDoCheck生命周期钩子中.您可以在" 如果您认为ngDoCheck意味着正在检查组件"中阅读更多内容- 请阅读本文.

有关更多详细信息,请参阅Angular中有关更改检测的所有信息.

  • 为什么detectChanges 对组件及其子组件起作用,而markForCheck 对组件和祖先起作用? (2认同)
  • 在这种情况下,我很困惑@MaxKoretskyi :(我阅读了你所有的教程并看到了你的演讲,我仍然无法理解这个问题:为什么当我调用 markForCheck() 时会触发更改检测?我知道它标记了组件及其前辈对于根组件来说都是脏的,并且将在“当前或下一个 CD 周期期间”对其进行检查。但是谁触发了下一个 CD 周期?我也尝试像您一样调试它,但无济于事。还有其他方法吗? /sf/ask/4491039731/ (2认同)

Kev*_*eal 18

cd.detectChanges() 将立即从当前组件向下通过其后代运行更改检测。

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

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


Ste*_*rov 16

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

在此处输入图片说明