Angular ChangeDetectionStrategy策略

287 阅读2分钟

Angular变更检测策略非常的强大,智能,以至于也带来一些痛点。检测策略会很容易被触发,例如: 点击事件,异步事件(用户交互,XHR)等。 组件中生效,但其他相邻兄弟组件不会被监测。这样就大大提升了响应速度,以免造成页面不必要的卡顿。

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})
export declare enum ChangeDetectionStrategy {
    /**
     * Use the `CheckOnce` strategy, meaning that automatic change detection is deactivated
     * until reactivated by setting the strategy to `Default` (`CheckAlways`).
     * Change detection can still be explicitly invoked.
     * This strategy applies to all child directives and cannot be overridden.
     */
    OnPush = 0,
    /**
     * Use the default `CheckAlways` strategy, in which change detection is automatic until
     * explicitly deactivated.
     */
    Default = 1
}
export declare abstract class ChangeDetectorRef {
    /**
     * When a view uses the {@link ChangeDetectionStrategy#OnPush OnPush} (checkOnce)
     * change detection strategy, explicitly marks the view as changed so that
     * it can be checked again.
     *
     * Components are normally marked as dirty (in need of rerendering) when inputs
     * have changed or events have fired in the view. Call this method to ensure that
     * a component is checked even if these triggers have not occured.
     *
     * <!-- TODO: Add a link to a chapter on OnPush components -->
     *
     */
    abstract markForCheck(): void;
    /**
     * Detaches this view from the change-detection tree.
     * A detached view is  not checked until it is reattached.
     * Use in combination with `detectChanges()` to implement local change detection checks.
     *
     * Detached views are not checked during change detection runs until they are
     * re-attached, even if they are marked as dirty.
     *
     * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
     * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->
     *
     */
    abstract detach(): void;
    /**
     * Checks this view and its children. Use in combination with {@link ChangeDetectorRef#detach
     * detach}
     * to implement local change detection checks.
     *
     * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
     * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->
     *
     */
    abstract detectChanges(): void;
    /**
     * Checks the change detector and its children, and throws if any changes are detected.
     *
     * Use in development mode to verify that running change detection doesn't introduce
     * other changes.
     */
    abstract checkNoChanges(): void;
    /**
     * Re-attaches the previously detached view to the change detection tree.
     * Views are attached to the tree by default.
     *
     * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
     *
     */
    abstract reattach(): void;
}

markForCheck()

当视图使用 OnPushcheckOnce)变更检测策略时,把该视图显式标记为已更改,以便它再次进行检查。 使用markForCheck时会标记当前节点及其父节点,爷节点一直到根节点为dirty状态,这样当有操作触发更新时,比如按钮点击,定时器等操作时会触发ngZone的事件拦截,触发applicationRef 的tick()方法进行全局的更新(组件标记为Onpush时只更新dirty状态的组件)。

 markForCheck() {
        markViewDirty(this._cdRefInjectingView || this._lView);
    }
function markViewDirty(lView) {
    while (lView) {
        lView[FLAGS] |= 64 /* Dirty */;
        const parent = getLViewParent(lView);
        // Stop traversing up as soon as you find a root view that wasn't attached to any container
        if (isRootView(lView) && !parent) {
            return lView;
        }
        // continue otherwise
        lView = parent;
    }
    return null;
}

image.png

image.png

detach()

从变更检测树中分离开视图。 已分离的视图在重新附加上去之前不会被检查。 与 detectChanges() 结合使用,可以实现局部变更检测。 detach会改变当前视图的标志,当组件树进行更新时,就算当前组件是dirty也不会触发更新。

detach() {
        this._lView[FLAGS] &= ~128 /* Attached */;
    }

detectChanges()

检查该视图及其子视图。与 detach 结合使用可以实现局部变更检测。

detectChanges() {
        detectChangesInternal(this._lView[TVIEW], this._lView, this.context);
    }
function detectChangesInternal(tView, lView, context) {
    const rendererFactory = lView[RENDERER_FACTORY];
    if (rendererFactory.begin)
        rendererFactory.begin();
    try {
        refreshView(tView, lView, tView.template, context);
    }
    catch (error) {
        handleError(lView, error);
        throw error;
    }
    finally {
        if (rendererFactory.end)
            rendererFactory.end();
    }
}

相当于调用refreshView()刷新当前视图及其子视图。

reattach()

把以前分离开的视图重新附加到变更检测树上。 视图会被默认附加到这棵树上。

reattach() {
        this._lView[FLAGS] |= 128 /* Attached */;
    }