>  기사  >  웹 프론트엔드  >  Angular 시리즈의 변경 감지 문제에 대한 자세한 해석

Angular 시리즈의 변경 감지 문제에 대한 자세한 해석

亚连
亚连원래의
2018-06-04 14:39:391441검색

이 글에서는 주로 Angular 시리즈의 변경 감지에 대한 자세한 설명을 소개하고 참고용으로 공유합니다.

Overview

간단히 말하면, 변경 감지는 뷰와 모델 사이에 바인딩된 값이 변경되었는지 감지하기 위해 Angular에서 사용됩니다. 변경되면 뷰에 동기화되고, 반대로 뷰에 바인딩된 값이 변경되는 것이 감지되면 해당 바인딩 함수가 콜백됩니다.

어떤 상황에서 변경 감지가 발생하나요?

요약하자면, 데이터가 변경될 수 있는 주로 다음과 같은 상황이 있습니다.

  1. 클릭, 제출 등의 사용자 입력 작업

  2. 서버 데이터(XHR) 요청

  3. Timed 이벤트, 예를 들어 setTimeout, setInterval

위의 세 가지 상황에는 한 가지 공통점이 있습니다. 즉, 바인딩 값을 변경하는 이러한 이벤트는 모두 비동기적으로 발생한다는 것입니다. 이러한 비동기 이벤트가 발생 시 Angular 프레임워크에 알릴 수 있다면 Angular 프레임워크는 시간에 따른 변화를 감지할 수 있습니다.

왼쪽은 실행될 코드를 나타냅니다. 여기서 스택은 Javascript의 실행 스택을 나타내고, webApi는 Javascript가 단일 스레드이기 때문에 브라우저에서 제공되는 일부 Javascript API를 나타냅니다. 작업은 작업 대기열에서 실행됩니다.

구체적으로 비동기 실행의 작동 메커니즘은 다음과 같습니다.

  1. 모든 동기 작업은 메인 스레드에서 실행되어 실행 컨텍스트 스택을 형성합니다.

  2. 메인 스레드 외에 "작업 대기열"도 있습니다. 비동기 작업에 실행 결과가 있는 한 이벤트는 "작업 대기열"에 배치됩니다.

  3. "실행 스택"의 모든 동기화 작업이 실행되면 시스템은 "작업 대기열"을 읽어 그 안에 어떤 이벤트가 있는지 확인합니다. 해당 비동기 작업은 대기 상태를 종료하고 실행 스택에 들어가 실행을 시작합니다.

  4. 메인 스레드는 위의 세 번째 단계를 계속 반복합니다.

위 코드가 Javascript에서 실행되면 먼저 func1이 실행 중인 스택에 들어갑니다. func1이 실행된 후 setTimeout이 실행 중인 스택에 들어갑니다. setTimeout이 실행되는 동안 콜백 함수 cb가 작업 대기열에 추가되고 setTimeout이 추가됩니다. func2 함수가 실행되면 실행 중인 스택이 비게 되고 작업 큐에 있는 cb가 실행 중인 스택에 들어가서 실행됩니다. 비동기 작업이 먼저 작업 대기열에 들어가는 것을 볼 수 있습니다. 실행 중인 스택의 모든 동기 작업이 실행되면 비동기 작업이 실행 중인 스택에 들어가서 실행됩니다. 이러한 비동기 작업의 실행 전후에 일부 Hook 기능을 제공할 수 있다면 Angular는 이러한 Hook 기능을 통해 비동기 작업의 실행을 알 수 있습니다.

angular2 변경 알림 받기

그래서 질문은, 각도2가 데이터가 변경되었음을 어떻게 알 수 있느냐는 것입니다. DOM을 수정해야 하는 위치와 가능한 가장 작은 범위에서 DOM을 수정하는 방법을 어떻게 알 수 있나요? 예, DOM을 가능한 한 작게 수정하십시오. 왜냐하면 DOM을 조작하는 것은 성능 측면에서 사치이기 때문입니다.

AngularJS에서는 $scope.$apply() 또는 $scope.$digest 코드에 의해 트리거되며 Angular는 Angular의 모든 비동기 이벤트를 모니터링하는 ZoneJS에 연결됩니다.

ZoneJS는 어떻게 작동하나요?

사실 Zone에는 Monkey Patching이라는 것이 있습니다. Zone.js가 실행되면 이러한 비동기 이벤트에 대한 프록시 패키징 계층이 만들어집니다. 즉, Zone.js가 실행된 후 setTimeout 및 addEventListener와 같은 브라우저 비동기 이벤트가 호출되면 기본 메서드가 더 이상 생성되지 않습니다. 호출되었지만 Monkey 패치된 프록시 메서드가 호출되었습니다. Hook 함수는 에이전트에 설정되어 있어 비동기 작업 실행 컨텍스트에 쉽게 들어갈 수 있습니다.

//以下是Zone.js启动时执行逻辑的抽象代码片段
function zoneAwareAddEventListener() {...}
function zoneAwareRemoveEventListener() {...}
function zoneAwarePromise() {...}
function patchTimeout() {...}
window.prototype.addEventListener=zoneAwareAddEventListener;
window.prototype.removeEventListener=zoneAwareRemoveEventListener;
window.prototype.promise = zoneAwarePromise;
window.prototype.setTimeout = patchTimeout;

변경 감지 프로세스

Angular의 핵심은 구성 요소화입니다. 그리고 구성 요소를 포함하면 구성 요소 트리가 생성됩니다. Angular의 변경 감지는 구성 요소로 나눌 수 있습니다. 각 구성 요소는 구성 요소의 종속성 주입을 통해 ChangeDetector에 해당합니다. 우리의 여러 컴포넌트는 트리 구조로 구성되어 있습니다. 하나의 컴포넌트가 ChangeDetector에 해당하므로 ChangeDetector도 트리 구조로 구성됩니다.

또한 Angular의 데이터 흐름은 상위 단방향 흐름에서 하향식입니다. 구성 요소에서 하위 구성 요소로. 단방향 데이터 흐름은 효율적이고 예측 가능한 변경 감지를 보장합니다. 상위 구성 요소를 확인한 후 하위 구성 요소가 상위 구성 요소의 데이터를 변경하므로 상위 구성 요소를 다시 확인해야 할 수 있습니다. 이는 권장되는 데이터 처리 방법이 아닙니다. 개발 모드에서 Angular는 위의 상황이 발생하면 2차 검사를 수행하여 오류를 보고합니다: 확인된 후 표현식이 변경되었습니다. 프로덕션 환경에서는 더티 검사가 한 번만 수행됩니다.

相比之下,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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.