Angular の変更検出メカニズムの簡単な分析

青灯夜游
リリース: 2022-12-15 21:20:55
転載
2343 人が閲覧しました

Angular の変更検出メカニズムの簡単な分析

変更検出とは何ですか?

アプリケーション開発プロセスでは、状態はアプリケーション上に表示する必要があるデータを表します。状態が変化すると、多くの場合、変化した状態を検出し、それに応じて対応するインターフェイスを更新するためのメカニズムが必要になります。このメカニズムは、変更検出メカニズムと呼ばれます。 [関連チュートリアルの推奨事項: "angular チュートリアル"]

WEB 開発では、アプリケーション インターフェイスを更新することは、実際には DOM ツリーを変更することになります。 DOM 操作はコストがかかるため、非効率的な変更検出はアプリケーションのパフォーマンスの低下を引き起こします。したがって、変更検出メカニズムを実装する際のフレームワークの効率がそのパフォーマンスを大きく左右します。

変更検出の実装方法

Angular は、コンポーネント データの変更を検出し、その変更を反映するためにビューを自動的に再レン​​ダリングできます。しかし、ボタンのクリックなどの低レベルのイベントの後に、どうやってこれを実行できるのでしょうか?

ゾーンを通じて、Angular は変更検出メカニズム を自動的にトリガーできます。

ゾーンとは何ですか?つまり、Zone は実行コンテキスト (実行コンテキスト) であり、実行環境として理解できます。一般的なブラウザ実行環境とは異なり、このリンクで実行されるすべての非同期タスクはタスクと呼ばれます。ゾーンはこれらのタスク用のフックを多数提供し、開発者が環境を簡単に「監視」できるようにします。すべての非同期タスク。

余談: Angular はオブザーバブル オブジェクト (Observable) の使用を強く推奨しているため、完全に Observable に基づいてアプリケーションを開発する場合は、Zone を置き換えてコール スタックを追跡する機能を実装できます。ゾーンを使用するよりもわずかに優れており、パフォーマンスが向上します。

  // Angular 在 v5.0.0-beta.8 起可以通过配置不使用 Zone 
  import { platformBrowser } from '@angular/platform-browser';
  platformBrowser().bootstrapModuleFactory(AppModuleNgFactory, { ngZone: 'noop' });
ログイン後にコピー

ブラウザのデフォルトのメカニズムをオーバーライドします

Angular は、起動時にブラウザの低レベル API (addEventListener など) を書き換えます。は、クリック処理を含むすべてのブラウザ イベントを登録するために使用されるブラウザ関数です。 Angular は、addEventListener をこれと同等の新しいものに置き換えます:

// this is the new version of addEventListener                                    
function addEventListener(eventName, callback) { 
    // call the real addEventListener                
    callRealAddEventListener(eventName, function() { 
        //first call the original callback              
        callback(...);
        // and then run Angular-specific functionality
        var changed = angular.runChangeDetection();
        if (changed) {
            angular.reRenderUIPart();
        }
    });
}
ログイン後にコピー

新しい addEventListener は、あらゆるイベント ハンドラーにさらに多くの機能を追加します。コールバックと呼ばれるのは登録だけではありません。変更検出を実行して UI を更新する機会があります。

ブラウザ非同期 API のサポート

変更検出をサポートするために、次の一般的なブラウザ メカニズムにパッチが適用されました:

  • すべてのブラウザ イベント (シングルクリック) 、マウスオーバー、キーの押下など)
  • setTimeout()andsetInterval()
  • Ajax HTTP リクエスト

実際、Zone.js は他の多くのブラウザ API にパッチを適用して、Websocket などの Angular 変更検出を透過的にトリガーします。

このメカニズムの制限の 1 つは、何らかの理由で Zone.js が非同期ブラウザ API をサポートしていない場合、変更検出がトリガーされないことです。これは、たとえば IndexedDB コールバックの場合に当てはまります。

デフォルトの変更検出メカニズムはどのように機能しますか?

すべての Angular コンポーネントには、アプリケーションの起動時に作成される変更検出機能が関連付けられています。例:

@Component({
    selector: 'todo-item',
    template: `<span class="todo noselect" 
       (click)="onToggle()">{{todo.owner.firstname}} - {{todo.description}}
       - completed: {{todo.completed}}</span>`
})
export class TodoItem {
    @Input()
    todo:Todo;

    @Output()
    toggle = new EventEmitter<Object>();

    onToggle() {
        this.toggle.emit(this.todo);
    }
}
ログイン後にコピー

このコンポーネントは、Todo オブジェクトを入力として受け取り、todo 状態が切り替わったときにイベントを発行します。

export class Todo {
    constructor(public id: number, 
        public description: string, 
        public completed: boolean, 
        public owner: Owner) {
    }
}
ログイン後にコピー

Todo には owner という属性があり、それ自体が 2 つのプロパティ (firstnamelastname) を持つオブジェクトであることがわかります。

変更検出器とはどのようなものですか?

変更検出器が実行時にどのように見えるかを実際に確認できます。これを確認するには、特定のプロパティにアクセスしたときにブレークポイントをトリガーするコードを Todo クラスに追加するだけです。

ブレークポイントにヒットすると、スタック トレースをたどって変更検出を表示できます。

Angular の変更検出メカニズムの簡単な分析

このメソッドは、最初は奇妙に見えるかもしれません。すべての変数です。奇妙なネーミング。しかし、さらに詳しく調べてみると、テンプレートで使用されている式ごとに、その式で使用されているプロパティの現在の値とそのプロパティの以前の値を比較するという、非常に単純なことを行っていることがわかりました。

前後のプロパティ値が異なる場合、isChanged が true に設定されます。それだけです。ほとんどの場合、looseNotIdentical() というメソッドを使用して値を比較します。

ネストされたオブジェクト owner はどうなるでしょうか?

変更検出コードでは、ネストされたオブジェクトの owner プロパティも相違点についてチェックされていることがわかります。ただし、firstname 属性のみが比較され、lastname 属性は比較されません。これは、lastname がコンポーネント template で使用されていないためです。同様に、Todo のトップレベルの id 属性は同じ理由で比較されません。

これにより、自信を持って言えます:

默认情况下,Angular Change Detection 通过检查模板表达式的值是否已更改来工作。

我们还可以得出结论:

默认情况下,Angular 不做深度对象比较来检测变化,它只考虑模板使用的属性

为什么默认情况下更改检测会这样工作?

Angular 的主要目标之一是更加透明和易于使用,因此框架用户不必费尽心思调试框架并了解内部机制即可有效地使用它。

如果 Angular 默认更改检测机制基于组件输入的参考比较而不是默认机制,那会是什么情况?即使是像 TODO 应用程序这样简单的东西也很难构建:开发人员必须非常小心地创建一个新的 Todo,而不是简单地更新属性。

OnPush 变化检测策略

如果你觉得默认模式影响了性能,我们也可以自定义 Angular 更改检测。将组件更改检测策略更新为OnPush

@Component({
    selector: &#39;todo-list&#39;,
    changeDetection: ChangeDetectionStrategy.OnPush,
    template: ...
})
export class TodoList {
    ...
}
ログイン後にコピー

现在让我们在应用程序中添加几个按钮:一个是通过直接改变列表的第一项来切换列表的第一项,另一个是向整个列表添加一个 Todo。代码如下所示:

@Component({
    selector: &#39;app&#39;,
    template: `<div>
                    <todo-list [todos]="todos"></todo-list>
               </div>
               <button (click)="toggleFirst()">Toggle First Item</button>
               <button (click)="addTodo()">Add Todo to List</button>`
})
export class App {
    todos:Array = initialData;

    constructor() {
    }

    toggleFirst() {
        this.todos[0].completed = ! this.todos[0].completed;
    }

    addTodo() {
        let newTodos = this.todos.slice(0);
        newTodos.push( new Todo(1, "TODO 4", 
            false, new Owner("John", "Doe")));
        this.todos = newTodos;
    }
}
ログイン後にコピー

现在让我们看看这两个新按钮的行为:

  • 第一个按钮“切换第一项”不起作用!这是因为该toggleFirst()方法直接改变了列表中的一个元素。
    TodoList无法检测到这一点,因为它的输入参考todos没有改变
  • 第二个按钮确实有效!请注意,该方法addTodo()创建了 todo 列表的副本,然后将项目添加到副本中,最后将 todos 成员变量替换为复制的列表。这会触发更改检测,因为组件检测到其输入中的参考更改:它收到了一个新列表!
  • 在第二个按钮中,直接改变 todos 列表是行不通的!我们真的需要一个新的清单。

OnPush只是通过引用比较输入吗?

情况并非如此。当使用 OnPush 检测器时,框架将在 OnPush 组件的任何输入属性更改、触发事件或 Observable 触发事件时检查

尽管允许更好的性能,但OnPush如果与可变对象一起使用,则使用会带来很高的复杂性成本。它可能会引入难以推理和重现的错误。但是有一种方法可以使使用OnPush可行。

使用 Immutable.js 简化 Angular 应用程序的构建

如果我们只使用不可变对象和不可变列表来构建我们的应用程序,则可以OnPush透明地在任何地方使用,而不会遇到更改检测错误的风险。这是因为对于不可变对象,修改数据的唯一方法是创建一个新的不可变对象并替换之前的对象。使用不可变对象,我们可以保证:

  • 新的不可变对象将始终触发OnPush更改检测
  • 我们不会因为忘记创建对象的新副本而意外创建错误,因为修改数据的唯一方法是创建新对象

实现不可变的一个不错的选择是使用Immutable.js库。该库为构建应用程序提供了不可变原语,例如不可变对象(映射)和不可变列表。

避免变更检测循环:生产与开发模式

Angular 更改检测的重要属性之一是,与 AngularJs 不同,它强制执行单向数据流:当我们的控制器类上的数据更新时,更改检测运行并更新视图。

如何在 Angular 中触发变更检测循环?

一种方法是如果我们使用生命周期回调。例如,在TodoList组件中,我们可以触发对另一个组件的回调来更改其中一个绑定:

ngAfterViewChecked() {
    if (this.callback && this.clicked) {
        console.log("changing status ...");
        this.callback(Math.random());
    }
}
ログイン後にコピー

控制台中将显示一条错误消息:

EXCEPTION: Expression &#39;{{message}} in App@3:20&#39; has changed after it was checked
ログイン後にコピー

仅当我们在开发模式下运行 Angular 时才会抛出此错误消息。如果我们启用生产模式会发生什么? 在生产模式下,错误不会被抛出,问题也不会被发现。

在开发阶段始终使用开发模式会更好,因为这样可以避免问题。这种保证是以 Angular 总是运行两次变更检测为代价的,第二次检测这种情况。在生产模式下,变更检测只运行一次。

打开/关闭变化检测,并手动触发它

在某些特殊情况下,我们确实想要关闭更改检测。想象一下这样一种情况,大量数据通过 websocket 从后端到达。我们可能只想每 5 秒更新一次 UI 的某个部分。为此,我们首先将更改检测器注入到组件中:

constructor(private ref: ChangeDetectorRef) {
    ref.detach();
    setInterval(() => {
      this.ref.detectChanges();
    }, 5000);
  }
ログイン後にコピー

正如我们所看到的,我们只是分离了变化检测器,这有效地关闭了变化检测。然后我们只需每 5 秒通过调用手动触发它detectChanges()

现在让我们快速总结一下我们需要了解的关于 Angular 变更检测的所有内容:它是什么,它是如何工作的以及可用的主要变更检测类型是什么。

概括

Angular 更改检测是一个内置的框架功能,可确保组件数据与其 HTML 模板视图之间的自动同步。

更改检测的工作原理是检测常见的浏览器事件,如鼠标点击、HTTP 请求和其他类型的事件,并确定每个组件的视图是否需要更新。

变更检测有两种类型:

  • 默认更改检测:Angular 通过比较事件发生前后的所有模板表达式值来决定是否需要更新视图,用于组件树的所有组件
  • OnPush 更改检测:这通过检测是否已通过组件输入或使用异步管道订阅的 Observable 将某些新数据显式推送到组件中来工作

Angular默认更改检测机制实际上与 AngularJs 非常相似:它比较浏览器事件之前和之后模板表达式的值,以查看是否有更改。它对所有组件都这样做。但也有一些重要的区别:

一方面,没有变化检测循环,也没有 AngularJs 中命名的摘要循环。这允许仅通过查看其模板和控制器来推理每个组件。

另一个区别是,由于变化检测器的构建方式,检测组件变化的机制要快得多。

最后,与 AngularJs 不同的是,变化检测机制是可定制的。

更多编程相关知识,请访问:编程教学!!

以上がAngular の変更検出メカニズムの簡単な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:juejin.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!