hsunny study blog

[Angular] Change Detection 처리 본문

programming/Angular

[Angular] Change Detection 처리

헤써니 2019. 8. 11. 02:59

서버의 통신을 받고 Component에 데이터를 뿌려 화면의 초기화를 진행합니다. 초기 셋팅이 완료된 화면에서 사용자의 액션에 따라 데이터의 값과 화면의 UI가 변경되어야 하는 경우(동기화)가 있습니다. 변경에 따른 이벤트를 단순히 EventEmmiter 를 이용한 OutputInput 만으로 Parent와 Child Component 간의 데이터 처리를 하는데에는 한계가 있었습니다.

 

angular change detection 를 구글링 하면 여러 해결 방법들이 나옵니다. ngZone , onChange() life cycle, DoCheck() life cycle, ChangeDetectionStrategy 등이 Change Detection 방법입니다. 찾아보면서 학습한 내용을 공유합니다. :)

 

Change Detection (CD)

뷰와 모델을 동기화할 수 있도록 컴포넌트의 변경을 감지하고 값을 동기화하는 프로세스입니다. Angular 의 Component들은 각각의 Change Detector 를 가지고 있습니다. Change Detector는 Tree 구조입니다.

 

 

ngZone

Zone API는 Angular2 에서 Change Detection 방법으로 새롭게 나왔습니다. Angular 2 이후부터는 ngZone 이라는 새로운 영역을 만들 수 있습니다. zone 안에 비동기 작업을 넣으면 됩니다. Zone 안에 넣는 비동기 작업들은 화면에 변화를 일으키는 작업들이기 때문입니다. ngZone은 비동기가 실행될 때 Angular 화면을 업데이트하는 작업을 할 수 있도록 지속적으로 감지합니다.

ngZone 의 데이터 감지방식은 아래와 같습니다.

 

앞서 말했듯이, Angular의 모든 Component들은 Change Detector를 가지고 있고, Tree 구조로 형성되어 있습니다. 데이터는 항상 위에서 아래로 흐릅니다. 따라서 루트 컴포넌트에서 변화가 발생하면, 구조대로 하위에 있는 모든 트리들에게 변화가 전파됩니다. 컴포넌트가 많아질수록 더 많은 곳에 데이터를 전파시키게 됩니다. 변화가 일어날 때마다 이 방식으로 전파가 일어나더라도, 앵귤러의 퍼포먼스는 걱정하지 않아도 됩니다. (It can perform hundreds of thousands of checks within a couple of milliseconds.This is mainly due to the fact that Angular generates VM friendly code.)

더 자세한 정보는... Check out [Victor Savkin’s](https://twitter.com/victorsavkin) talk on [Change Detection Reinvented](https://www.youtube.com/watch?v=jvKGQSFQf10) for a deeper explanation on this.

 

Smarter Change Detection

기본적으로 Angular 는 위의 방식대로 모든 Component들을 점검하지만, 상태를 바꾼 일부 부분에 대해서만 변경 감지를 실행할 수도 있습니다. 모든 Component들을 감지하는 것을 Angular가 커버할 수는 있지만, 상태가 바뀐 일부 부분에 대해서만 변경 감지를 하도록 하면 더욱 빨라질 수 있을 것입니다. 이 방법으로 ChangeDetectionStrategy 를 사용합니다.

// API > @angular/core
enum ChangeDetectionStrategy {
  OnPush: 0
  Default: 1
}
아래 내용 전체는 해외 블로그의 글을 번역하였습니다.
의역, 오역이 있을 수 있습니다.
잘못된 부분에 대한 지적 환영합니다.!
원문보기

이변성(dic. 변하기 쉬운, 변덕)에 대한 이해

불변의 데이터 구조가 도움이 될 수 있는 이유와 방법을 이해하기 위해서, 우리는 이변성이 무엇을 의미하는지 이해할 필요가 있습니다. 다음 component를 가정해보겠습니다.

VCardApp은 input 프로퍼티인 vData 를 가지고 있는 <v-card> 를 child component로 사용합니다. VCardApp 의 vData 프로퍼티를 해당 component에 데이터를 전달합니다. vData 는 두개의 프로퍼티를 가지고 있는 객체입니다. 또한, vData 의 name 값을 변화시키는 changeData() 메소드가 존재합니다.

 

changeData() 가 name 프로퍼티를 변경하여 vData를 변경한다는 부분은 아주 중요합니다. 프로퍼티가 변경된다고 할지라도, vData 참조 자체는 동일하게 유지됩니다.

 

일부 이벤트들로 인해 changeData() 가 실행된다고 가정하면, change detection이 수행될 때 어떤일이 발생할까요? 먼저, vData.name 의 값은 변하고, 그 후 <v-card> 로 값이 전달될 것입니다. <v-card> 의 change detector는 vData 가 전달된다면 이전의 값과 같은지 확인합니다. 참조자체는 변하지 않았지만, name 프로퍼티는 변했기 때문에 Angular는 change detection 을 수행합니다.

 

JavaScript(원시값 제외)에서는 객체들은 기본적으로 변경가능하므로, Angular는 보수적이어야 하고, 이벤트가 발생할 때마다 모든 component들에 대해 change detection 을 수행해야 합니다. 여기서 불변한 데이터 구조가 작동하게 됩니다.

 

불변한 객체

불변한 객체는 우리에게 객체가 변하지 않을 것이라는 것을 보장합니다. 즉, 만약 불변한 객체를 사용하고 그러한 객체를 변경하기를 원한다면, 원래의 객체는 불변하기 때문에, 항상 그 변화에 대한 새로운 참조를 얻어야 합니다.

 

위의 내용을 증명하는 수도코드입니다:

someAPIForImmutables 는 불변한 객체 구조를 사용하기 위해 필요한 API입니다. 그러나, 보는 것과 같이, name 프로퍼티를 간단하게 변경할 수는 없습니다. 특정한 변화와 함께 새로운 객체를 얻게 될 것이고, 이 객체는 새로운 참조를 갖게 될 것입니다. 또는, 간단히 말해서: 변화가 있다면, 새로운 참조를 얻게 됩니다.

 

체크하는 횟수 줄이기

Angular는 input 프로퍼티의 값이 변화하지 않았을 때, 전체 change detection 을 건너 뛸 수 있습니다. 앞에서 '변화'는 '새 참조'를 의미한다는 것을 배웠습니다. 만약 Angular에서 불변할 객체를 사용한다면, input이 변하지 않았다면 component는 change detection을 건너뛰어도 된다고 Angular에 말해주어야 합니다.

 

<v-card> 를 보면서 어떻게 동작하는지 살펴봅시다.

 

보다시피 VCardCmp 는 input 프로퍼티들에만 의존합니다. change detection strategy를 Onpush로 설정하여 input이 변경되지 않았을 경우 component의 서브트리를 위한 change detection을 건너뛰라고 Angular에 말해줄 수 있습니다.

 

다 됐습니다! 이제 더 큰 component 트리가 있다고 상상해보세요. 불변한 객체가 사용되고 이에 대한 정보를 Angular 가 알릴 때 서브트리 전체를 건너뛸 수 있습니다.

 

[Jurgen Van De Moere](https://twitter.com/jvandemo) has written an [in-depth article](http://www.jvandemo.com/how-i-optimized-minesweeper-using-angular-2-and-immutable-js-to-make-it-insanely-fast/) on how he made a minesweeper game built with Angular and Immutable.js blazingly fast. Make sure to check that one out.

Observables

앞서 말했듯이, Observables 는 변화가 발생했을 때 확실하게 보장해줍니다. 불변한 객체와는 다르게, Observables는 변화가 생겼을 때 새로운 참조 주지 않습니다. 대신에, 반응하기 위해 구독하고 있는 이벤트를 발생시킵니다.

 

그래서, 만약 우리가 Obsevables를 사용하고 서브트리의 change detectore를 건너뛰기 위하여 OnPush 를 사용하고 싶지만 이 객체들의 참조는 절대 변하지 않습니다. 그렇다면 어떻게 다루어야 할까요? Angular는 component 트리의 경로를 특정이벤트를 위해 체크할 수 있도록 하는 아주 스마트한 방법을 가지고 있습니다. 이 경우가 우리가 필요로 하는 경우입니다.

 

무엇을 말하는 건지 이해하기 위해 아래의 component를 살펴보겠습니다.

쇼핑카트가 있는 쇼핑몰 어플리케이션을 만든다고 가정해봅시다. 사용자가 쇼핑카트로 물건을 담을 때마다 UI에 몇개를 담았는지 카운트하여 사용자가 카트안에 상품이 몇개가 있는지 볼 수 있도록 하고 싶습니다.

 

또한, change detection strategy를 OnPush 로 설정하기 때문에 component의 input 프로퍼티가 변경되었을 때만 change detection이 매번 수행되지 않습니다.

 

그러나, 앞서 언급한 대로, addItemSteram 의 참조가 변하지 않는다면 ,change detection은 이 component의 서브트리를 에는 change detection이 수행되지 않습니다. 이 문제는 component가 ngOnInit() life cycle 안에서 해당 스트림을 구독하고 있기 때문에 발생합니다. 이는 애플리케이션 상태 변화이며 뷰에 반영하고 합니다.

 

change detector 트리는 아래와 같습니다 (모든 것을 OnPush 로 설정). 이벤트가 발생했을 때  change detection이 발생하지 않습니다.

 

 

어떻게 하면 Angular에게 이 변화를 알릴 수 있을까요? 모든 트리가 OnPush 로 설정되어 있음에도 불구하고, Angular 에게 컴포넌트에 change detection이 필요하다고 어떻게 말할 수 있을까요?

 

걱정마세요, Angular는 커버할 수 있습니다. 앞서 살펴본 바와 같이, change detection은 항상 위에서 아래로 수행됩니다. 그래서 우리에게 필요한 것은 변화가 발생한 component에서 트리의 전체 경로에 변화를 감지하는 방법입니다. Angular 는 알 수 없지만, 우리는 알고 있습니다.

 

의존성 주입을 통해 component의 ChangeDetectorRef 에 접근할 수 있으며, markForCheck() 라고 불리는 API가 함께 존재합니다. 이 메소드가 우리가 원하는 것입니다! 다음 change detection 실행을 위해 루트를 확인할 때까지 component의 경로를 표시합니다.

 

컴포넌트에 인젝션합니다:

 

 

그 후에, Angular에게 루트를 확인할 때까지 이 component의 경로를 표시하라고 말해줍니다.

 

 

관찰 가능한 이벤트가 발생한 후 change detection이 시작되기 전의 모습은 아래와 같습니다.

 

 

change detection 이 수행되면, 위에서 아래로 이동합니다.

 

 

[참고사이트]

- https://bkim.tistory.com/10

- https://www.joshmorony.com/understanding-zones-and-change-detection-in-ionic-2-angular-2/

- https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html