详谈Angular变更检测机制(一)(angular更新视图)

Angular是Google公司一个基于TypeScript的开源Web应用程序框架,被许多前端项目和开发者所使用。本人也是使用者之一,今天想来谈谈Angular的一项核心技术——变更检测(Change Detection),如有不正确的地方,欢迎大家给予指正。

一、什么是变更检测?

变更检测是Angular应用的核心特性之一,它负责同步模型(Model)和视图(View)之间的状态。当模型状态发生改变时,Angular的变更检测机制将确保这些更改反映在视图上。反之,当用户与视图进行交互(例如点击按钮或填写表单)时,这些更改也将同步到模型中。

二、如何实现变更检测?

Angular能够实现变更检测这一机制,始于Angular的Zone.js库。Zone.js是一个为异步操作提供上下文环境的库,使我们可以追踪代码中的异步操作。

在Angular中,每当我们启动一个应用,Zone.js就会自动地在主函数bootstrapModule的周围创建一个新的Zone,被称为Angular Zone。所有在Angular Zone中运行的异步任务,都会被Zone.js跟踪,并在完成时触发变更检测。下面是这个过程中的一段源代码:

class NgZone {

// ...


run(fn: Function, applyThis?: any, applyArgs?: any): any {

return this.inner.run(fn, applyThis, applyArgs);

}


// ...

}

NgZone类提供了一种方式,让我们可以在Angular Zone内运行函数。当我们在Angular Zone内运行函数时,Zone.js就会知道何时启动和完成异步任务,从而触发变更检测。

当异步任务(例如Promise,setTimeout,或用户交互事件)在Angular Zone中完成时,NgZone就会触发一个事件,通知Angular进行变更检测。这是通过ApplicationRef.tick()方法实现的,该方法会更新视图,并触发任何需要的变更检测。下面是这个过程的源代码:

class ApplicationRef {

// ...


tick(): void {

this.zone.run(() => {

// Perform change detection for all components

this._views.forEach((view) => view.detectChanges());

视图的变更检测是通过ViewRef.detectChanges方法来实现的:

detectChanges() {

// 检查视图是否被销毁

if (this._view.state & ViewState.Destroyed) {

throw new Error('Cannot detect changes on a destroyed view.');

}


// 执行视图的变更检测

Services.checkAndUpdateView(this._view);

}

Services.checkAndUpdateView方法中,Angular 会遍历视图的所有节点,并对每个节点进行变更检测。具体的变更检测逻辑取决于节点的类型,例如,对于组件节点,Angular 会检查其输入属性是否发生改变。

Angular中ChangeDetectorRef类提供了一些让我们可以更直接地控制变更检测的方法。

abstract class ChangeDetectorRef {

// ...


abstract markForCheck(): void;

abstract detach(): void;

abstract checkNoChanges(): void;

abstract reattach(): void;

abstract detectChanges(): void;


// ...

}

markForCheck():显式地标记视图为已更改,以便再次检查。通常当组件的输入属性发生改变或者视图中发生了事件时,组件会被标记为脏的(需要重新渲染)。调用此方法可以确保即使这些触发器没有发生,组件也会被检查。

detach():将此视图从变更检测树中分离。被分离的视图不会被检查,直到它被重新附加。此方法可以与detectChanges() 结合使用,以实现本地变更检测检查。


checkNoChanges():检查变更检测器及其子视图,并在检测到任何更改时抛出异常。在开发模式下使用它以验证运行变更检测不会引入其他更改,在生产模式下调用它是一个空操作。


reattach():重新附加先前分离的视图到变更检测树。默认情况下,视图是附加到树的。


detectChanges():检查这个视图及其子视图。此方法可以与 detach() 结合使用,以实现本地变更检测检查。


要注意的是,这些方法的使用应该非常谨慎,因为不恰当的使用可能会导致意想不到的结果,如性能下降,视图状态不同步等。在大多数情况下,你应该让Angular自动处理变更检测,只有在特定的性能优化或复杂场景中,你可能需要手动地控制变更检测。由于文章的篇幅问题,这5个方法我会放到后续文章中详细介绍,欢迎大家持续关注。

三、变更检查策略

除了了解变更检测的工作原理,理解变更检测策略也是非常重要的。Angular提供了两种变更检测策略:Default 和 OnPush。

默认策略Default是所有组件默认的变更检测策略。在这种策略下,无论何时应用状态改变,Angular都会检查整个组件树。这意味着即使一个组件的状态没有改变,只要应用的状态改变,Angular也会检查该组件。


而OnPush策略则更加高效。如果一个组件使用了OnPush策略,那么只有当该组件的输入属性改变时,Angular才会检查该组件和其子组件。这意味着如果应用的状态改变,但不影响到该组件,那么Angular就不会检查该组件,从而提高了应用的性能。


要使用OnPush策略,你只需要在组件的装饰器中设置 changeDetection 属性:

@Component({

selector: 'my-component',

template: `<div>My Component</div>`,

changeDetection: ChangeDetectionStrategy.OnPush

})

class MyComponent {

// ...

}

使用OnPush策略的组件,如果需要在非输入属性变更的情况下触发变更检测,可以通过 ChangeDetectorRef 的 markForCheck() 方法来标记组件为需要检查的状态。

还有一些与变更检测相关的其他方法和类,如 ChangeDetectorRef 和 ViewContainerRef 的方法。这些方法涉及到了一些更深层次的内容,包括如何创建和获取 ChangeDetectorRef 实例,以及在某些特定场景下(如管道中)如何正确地使用它。后续文章会谈到,欢迎大家持续关注,评论区交流前端开发经验。#前端框架##前端面试##暑期创作大赛#