ホームページ  >  記事  >  ウェブフロントエンド  >  Angular シリーズにおける変更検出の問題の詳細な解釈

Angular シリーズにおける変更検出の問題の詳細な解釈

亚连
亚连オリジナル
2018-06-04 14:39:391415ブラウズ

この記事では主にAngularシリーズのChange Detectionの詳細な説明を紹介しますので、参考にしてください。

概要

簡単に言うと、変更検出は、モデル内でバインドされた値が検出されたときに、ビューとモデルの間でバインドされた値が変更されたかどうかを検出するために Angular によって使用されます。変更された場合はビュー上に同期されます。逆に、ビュー上でバインドされた値が変更されたことが検出された場合は、対応するバインディング関数がコールバックされます。

どのような状況で変更検出が発生しますか?

要約すると、データが変更される可能性のある主な状況は次のとおりです:

  1. クリックや送信などのユーザー入力操作

  2. サーバーデータ(XHR)のリクエスト

  3. 時間指定イベント、たとえば、setTimeout、setInterval

上記の 3 つの状況には共通点が 1 つあります。それは、バインディング値の変更を引き起こすこれらのイベントはすべて非同期で発生するということです。これらの非同期イベントが発生したときに Angular フレームワークに通知できれば、Angular フレームワークは時間の変化を検出できます。

ここでのスタックは実行されるコードを表し、webApi はブラウザで提供されるいくつかの Javascript API です。JavaScript はシングルスレッドであるためです。タスクはタスクキューで実行されます。

具体的には、非同期実行の動作メカニズムは次のとおりです:

  1. すべての同期タスクはメインスレッド上で実行され、実行コンテキストスタックを形成します。

  2. メインスレッドの他に「タスクキュー」もあります。非同期タスクに実行結果がある限り、イベントは「タスク キュー」に配置されます。

  3. 「実行スタック」内のすべての同期タスクが実行されると、システムは「タスクキュー」を読み取り、その中にどのようなイベントがあるかを確認します。これらの対応する非同期タスクは待機状態を終了し、実行スタックに入り、実行を開始します。

  4. メインスレッドは上記の 3 番目のステップを繰り返し続けます。

上記のコードが Javascript で実行されると、最初に func1 が実行スタックに入り、setTimeout の実行中に、コールバック関数 cb がタスク キューに追加され、次に setTimeout が追加されます。 func2 関数が実行されると、実行中のスタックは空になり、タスク キュー内の cb が実行中のスタックに入り、実行されます。実行スタック内のすべての同期タスクが実行されると、非同期タスクが最初にタスク キューに入り、実行スタックに入れられて実行されることがわかります。これらの非同期タスクの実行の前後に何らかのフック関数を提供できれば、Angular はこれらのフック関数を通じて非同期タスクの実行を知ることができます。

angular2 変更通知を取得する

そこで質問ですが、angular2 はデータが変更されたことをどのようにして知るのでしょうか? DOM のどこを変更する必要があるのか​​、また可能な限り最小の範囲で DOM を変更する方法はどのようにしてわかりますか?はい、DOM の変更はできるだけ小さくします。DOM を操作するのはパフォーマンスを考えると贅沢だからです。

AngularJS では、コード $scope.$apply() または $scope.$digest によってトリガーされ、Angular は ZoneJS に接続され、Angular のすべての非同期イベントを監視します。

ZoneJS はどのように行うのですか?

実はZoneにはモンキーパッチングと呼ばれるものがあります。 Zone.js の実行中、これらの非同期イベント用にプロキシ パッケージ化のレイヤーが作成されます。つまり、Zone.js の実行後、setTimeout や addEventListener などのブラウザーの非同期イベントが呼び出されるとき、ネイティブ メソッドは使用されなくなります。と呼ばれますが、Monkey パッチを適用したプロキシ メソッドが呼び出されます。エージェントにはフック関数が設定されており、これらのフック関数を通じて、非同期タスク実行のコンテキストに簡単に入ることができます

変更検出のプロセス

Angular の核心はコンポーネント化です。コンポーネントを埋め込むと、コンポーネント ツリーが生成されます。 Angular の変更検出は、コンポーネントに分割できます。各コンポーネントは、コンポーネント内の依存関係注入を通じて、changeDetector に対応します。複数のコンポーネントはツリー構造で構成されており、1 つのコンポーネントがchangeDetectorに対応するため、changeDetectorもツリー構造で構成されています

さらに、Angularのデータフローは親からの一方向フローです。コンポーネントからサブコンポーネントへ。一方向のデータ フローにより、効率的かつ予測可能な変更検出が保証されます。ただし、親コンポーネントをチェックした後、子コンポーネントが親コンポーネントのデータを変更するため、親コンポーネントを再度チェックする必要がある場合がありますが、これは推奨されるデータ処理方法ではありません。開発モードでは、Angular は二次チェックを実行します。上記の状況が発生した場合、二次チェックはエラー「チェックされた後に式が変更されました」エラーを報告します。実稼働環境では、ダーティ チェックは 1 回だけ実行されます。

相比之下,AngularJS采用的是双向数据流,错综复杂的数据流使得它不得不多次检查,使得数据最终趋向稳定。理论上,数据可能永远不稳定。AngularJS给出的策略是,脏检查超过10次,就认为程序有问题,不再进行检查。

变化检测策略

Angular有两种变化检测策略。Default是Angular默认的变化检测策略,也就是上述提到的脏检查,只要有值发生变化,就全部从父组件到所有子组件进行检查,。另一种更加高效的变化检测方式:OnPush。OnPush策略,就是只有当输入数据(即@Input)的引用发生变化或者有事件触发时,组件才进行变化检测。

defalut 策略

main.component.ts

@Component({
 selector: 'app-root',
 template: `
 <h1>变更检测策略</h1>
 <p>{{ slogan }}</p>
 <button type="button" (click)="changeStar()"> 改变明星属性
 </button>
 <button type="button" (click)="changeStarObject()">
   改变明星对象
 </button>
 <movie [title]="title" [star]="star"></movie>`,
})
export class AppComponent {
 slogan: string = &#39;change detection&#39;;
 title: string = &#39;default 策略&#39;;
 star: Star = new Star(&#39;周&#39;, &#39;杰伦&#39;);
 changeStar() {
  this.star.firstName = &#39;吴&#39;;
  this.star.lastName = &#39;彦祖&#39;;
 }
 changeStarObject() {
  this.star = new Star(&#39;刘&#39;, &#39;德华&#39;);
 } 
}

movie.component.ts

@Component({
 selector: &#39;movie&#39;,
 styles: [&#39;p {border: 1px solid black}&#39;],
 template: `
<p>
<h3>{{ title }}</h3>
<p>
<label>Star:</label>
<span>{{star.firstName}} {{star.lastName}}</span>
</p>
</p>`,

})
export class MovieComponent {
 @Input() title: string;
 @Input() star;
}

上面代码中, 当点击第一个按钮改变明星属性时,依次对slogan, title, star三个属性进行检测, 此时三个属性都没有变化, star没有发生变化,是因为实质上在对star检测时只检测star本身的引用值是否发生了改变,改变star的属性值并未改变star本身的引用,因此是没有发生变化。

而当我们点击第二个按钮改变明星对象时 ,重新new了一个 star ,这时变化检测才会检测到 star发生了改变。

然后变化检测进入到子组件中,检测到star.firstName和star.lastName发生了变化, 然后更新视图.

OnPush策略

与上面代码相比, 只在movie.component.ts中的@component中增加了一行代码:

changeDetection:ChangeDetectionStrategy.OnPush
此时, 当点击第一个按钮时, 检测到star没有发生变化, ok,变化检测到此结束, 不会进入到子组件中, 视图不会发生变化.

当点击第二个按钮时,检测到star发生了变化, 然后变化检测进入到子组件中,检测到star.firstName和star.lastName发生了变化, 然后更新视图.

所以,当你使用了OnPush检测机制时,在修改一个绑定值的属性时,要确保同时修改到了绑定值本身的引用。但是每次需要改变属性值的时候去new一个新的对象会很麻烦,immutable.js 你值得拥有!

变化检测对象引用

通过引用变化检测对象ChangeDetectorRef,可以手动去操作变化检测。我们可以在组件中的通过依赖注入的方式来获取该对象:

constructor(
  private changeRef:ChangeDetectorRef
 ){}

变化检测对象提供的方法有以下几种:

  1. markForCheck() - 在组件的 metadata 中如果设置了 changeDetection:ChangeDetectionStrategy.OnPush 条件,那么变化检测不会再次执行,除非手动调用该方法, 该方法的意思是在变化监测时必须检测该组件。

  2. detach() - 从变化检测树中分离变化检测器,该组件的变化检测器将不再执行变化检测,除非手动调用 reattach() 方法。

  3. reattach() - 重新添加已分离的变化检测器,使得该组件及其子组件都能执行变化检测

  4. detectChanges() - 从该组件到各个子组件执行一次变化检测

OnPush策略下手动发起变化检测

组件中添加事件改变输入属性

在上面代码movie.component.ts中修改如下

@Component({
 selector: &#39;movie&#39;,
 styles: [&#39;p {border: 1px solid black}&#39;],
 template: `
<p>
<h3>{{ title }}</h3>
<p>
<button (click)="changeStar()">点击切换名字</button>    
<label>Star:</label>
<span>{{star.firstName}} {{star.lastName}}</span>
</p>
</p>`,
changeDetection:ChangeDetectionStrategy.OnPush
})
export class MovieComponent {
 constructor(
  private changeRef:ChangeDetectorRef
 ){}
 @Input() title: string;
 @Input() star;
 
 changeStar(){
  this.star.lastName = &#39;xjl&#39;;
 }
}

此时点击按钮切换名字时,star更改如下

![图片描述][3]

第二种就是上面讲到的使用变化检测对象中的 markForCheck()方法.

ngOnInit() {
  setInterval(() => {
   this.star.lastName = &#39;xjl&#39;;
   this.changeRef.markForCheck();
  }, 1000);
 }

输入属性为Observable

修改app.component.ts

@Component({
 selector: &#39;app-root&#39;,
 template: `
 <h1>变更检测策略</h1>
 <p>{{ slogan }}</p>
 <button type="button" (click)="changeStar()"> 改变明星属性
 </button>
 <button type="button" (click)="changeStarObject()">
   改变明星对象
 </button>
 <movie [title]="title" [star]="star" [addCount]="count"></movie>`,
})
export class AppComponent implements OnInit{
 slogan: string = &#39;change detection&#39;;
 title: string = &#39;OnPush 策略&#39;;
 star: Star = new Star(&#39;周&#39;, &#39;杰伦&#39;);
 count:Observable<any>;

 ngOnInit(){
  this.count = Observable.timer(0, 1000)
 }
 changeStar() {
  this.star.firstName = &#39;吴&#39;;
  this.star.lastName = &#39;彦祖&#39;;
 }
 changeStarObject() {
  this.star = new Star(&#39;刘&#39;, &#39;德华&#39;);
 } 
}

此时,有两种方式让MovieComponent进入检测,一种是使用变化检测对象中的 markForCheck()方法.

ngOnInit() {
  this.addCount.subscribe(() => {
   this.count++;
   this.changeRef.markForCheck();
  })

另外一种是使用async pipe 管道

@Component({
 selector: &#39;movie&#39;,
 styles: [&#39;p {border: 1px solid black}&#39;],
 template: `
<p>
<h3>{{ title }}</h3>
<p>
<button (click)="changeStar()">点击切换名字</button>    
<label>Star:</label>
<span>{{star.firstName}} {{star.lastName}}</span>
</p>
<p>{{addCount | async}}</p>
</p>`,
 changeDetection: ChangeDetectionStrategy.OnPush
})

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

利用vue实现如何在表格里,取每行的id(详细教程)

利用vue移动端UI框架如何实现QQ侧边菜单(详细教程)

使用vue及element组件的安装教程(详细教程)

以上がAngular シリーズにおける変更検出の問題の詳細な解釈の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。