详谈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 实例,以及在某些特定场景下(如管道中)如何正确地使用它。后续文章会谈到,欢迎大家持续关注,评论区交流前端开发经验。#前端框架##前端面试##暑期创作大赛#