Change Detection 란 무엇인가?
Change Detection(이하 CD)는 뷰와 모델을 동기화 할 수 있도록 컴포넌트의 변경을 감지하고 값을 동기화 하는 프로세스다.
Angular JS (1.x)
뷰단의 변경을 감지하는 것 뿐만 아니라, 비동기로 돌아가는 자바스크립트(ajax, setTimeout 등..)가 데이터 변경하는 시점을 감지하여 모델의 변경의 뷰단에 반영해야 하는데 Angular 1에서는 이런 로직을 Digest Loop로 구현 했었다.
Digest Loop로 감시할 대상을 지정하기 위해 scope마다 watcher을 등록하여 감지할 목록들을 만들어야 했는데 이런 상황 때문에, 데이터의 변경을 감지하기 위해서는 Angular 도구만을 사용해야 했다. ajax call 또는 click 같은 이벤트도 $http와 ng-click으로 구현해야만 모델 변경이 감지되었다.
이를 보완하기 위해 명시적 호출($diest, $apply, $timeout)을 지원했지만 개발자가 직접호출해야한다는 이질감이 있었다.
또한 Angular 1은 양방향 데이터 바인딩으로 구현했기 때문에 watcher들이 늘어날 수록 Digest Loop은 걷잡을 수 없이 혼란스러워졌다.
Angular
Angular2에서는 $scope기능을 없애고 Zone.js를 이용하여 CD를 구현하였는데, Zone.js 덕분에 Angular 도구 뿐만 아니라, 순수 자바스크립트로 컴포넌트를 구성할 수 있게 되었다. 또한 Angular 2는 단방향 바인딩을 구현하였기 때문에 (ngModel을 사용해서 양방향 바인딩을 구현할때도 데이터의 흐름은 단방향) CD는 어플리케이션 전체를 변경할 필요 없이 데이터가 바인딩 되어있는 곳만 변경을 감지하고 Angular2는 변경된 부분만 다시 그려주면 되었다. 이를 통해 Angular는 성능을 높일 수 있었고 강제되던 Angular 도구들을 제거할 수 있었다.
Figure: File Structure 출처 - https://angular-2-training-book.rangle.io
어떻게 변화를 감지하는가? (Zone.js - 동영상)
Angular는 Angular도구가 아닌 순수 자바스크립트로 변경한 데이터 변경 또한 감지 할 수 있다고 했다.
우리가 웹에서 ajax로 데이터로 가져오거나, Timer로 함수를 실행하거나, click 이벤트를 발생시키는것은 모두 비동기로 이루어지는데 사용자가 명시적으로 호출하지 않고도 어떻게 Angular는 어플리케이션 상태 변화를 감지할 수 있을까?
답은 Zone.js에 있었다. (Angular는 자체적인 Zone을 가지고 있고 ngZone이라고 한다.)
Zone.js는 JAVA의 ThreadLocal 개념과 비슷해서 각각의 비동기 기능에서 kick on과 kick off를 설정하지 않아도 Zone이 이를 감지하여 후킹할수 있게 해준다.
위 코드를 실행해보면 콘솔 창에 다음과 같이 나타단다.
1번에서 click event listener가 등록된것을
2번에서 setTimeout이 발생하여 Invoike 한것을 상세하게 알 수 있다.
이렇게 Zone.js는 비동기 코드들을 컨트롤하면서 사용자에게 편리한 후킹인터페이스를 제공해주는 것이고,
Angular는 ngZone을 이용해서 컴포넌트의 변화를 감지하는 것이다.
Angular에서 구현된 소스는 다음과 같다.
this.zone.onMicrotaskEmpty
.subscribe(() => {
this.zone.run(() => this.tick()
})
})
tick()
{
this.changeDetectorsRefs
.forEach((ref) => ref.detectChanges())
}
어떻게 변경하는가?
그렇다면 앞서 ngZone이 감지한 변화를 Angular는 어떻게 View에 적용할까?
Angular의 컴포넌트들은 각자 자신만의 CD를 가지고 있어서, 각자 CD 수행하고 컨트롤할 수 있다. 예를 들어 클릭 이벤트가 발생하면, Zone.js가 이벤트를 감지하여 알려주고 Angular는 CD를 실행한다. 이 때 컴포넌트는 각자 자신의 CD를 가지고 있고 컴포넌트는 트리구조로 이루어져 있기 때문에 CD의 구조도 방향이 있는 트리구조로 구성된다. 방향성이 있는 트리구조 때문에 CD는 항상 TOP에서 ROOT로 전파된다.
http://pascalprecht.github.io/slides/angular-2-change-detection-explained/#/59
Angular 컴포넌트들은 자신만의 Change Detector를 가진다?
Runtime에 Angular는 각 컴포넌트마다 자신이 가진 속성들에 대하여 Change Detector 클래스를 생성한다.
Change detector들은 컴포넌트의 각 속성에대한 Old Value를 가지고 있기때문에 독립적으로 자신의 컴포넌트에 대한 변경 여부를 확인 할 수 있고,
감지 속도를 높이기 위해 개발자가 전략을 작성하거나 속성값 등을 전달 할 수 있다.
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* @stable
*/
export declare abstract class ChangeDetectorRef {
abstract markForCheck(): void;
/**
* Detaches the change detector from the change detector tree.
*/
abstract detach(): void;
/**
* Checks the change detector and its children.
*/
abstract detectChanges(): void;
/**
* Checks the change detector and its children, and throws if any changes are detected.
*/
abstract checkNoChanges(): void;
/**
* Reattach the change detector to the change detector tree.
*/
abstract reattach(): void;
}
Change Detection을 빠르게
앞서 설명했듯이 CD는 Runtime시 각 컴포넌트마다 생성되기 때문에 다양한 형태를 가질 수 있다. 각 컴포넌트의 구조에 맞게 CD 클래스를 정의 하고 생성하기 때문에, CD가 감시해야할 대상을 줄이면 성능을 높일 수 있다. Angular는 CD가 감시해야 할 대상을 줄이기 위해 Immutable 객체와 Observable 객체를 알아?보고 감시 대상에서 제외해 할 수 있는데, 이런 최적화는 Angular의 OnPush 전략(컴포넌트에 바인딩된 Input 값의 변화가 없는 경우에는 Subtree 에 대해 Change Detection을 하지 않음)으로 설정하여 처리한다.
Immutable
Immutable 객체는 값의 변화가 없음을 보장한다. 새로운 값을 지정하고 싶으면 객체를 변경하는 것이 아니라 새로 생성 해야만한다.
객체를 새로 생성한다는 것은 Angular 입장에서 값(상태) 비교가 필요 없다는 것을 뜻한다. Immutable 객체를 사용하는 바인딩된 컴포넌트들은 변경된 객체의 상태를 비교해 볼 필요 없이 객체의 레퍼런스만으로 변화를 감지할 수 있다.
(Javascript Immutable lib들을 이용하여 손쉽게 Immutable 객체를 생성해 볼 수 있는다. Immutable.js Github)
http://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html
Observable
관찰할 수 있는, 식별할 수 있는 객체를 의미한다. 관찰할 수 있는 객체를 구독(subscribe)하면서 값을 참조하는 방법이다.
예를 들어 다음과 같은 코드가 있을 때, firstName만 변경하여도 fullName까지 함께 변경 되는 것이다.
var firstName = Observable('BK');
var lastName = Observable('KIM');
var fullName = Observable(function () {
return firstName.value + ' ' + lastName.value;
});
Angular에서는 Immutable 객체 반대 방식으로 CD를 생략한다. Immutable 객체의 레퍼런스 참조만으로 CD를 생략할 수 있었다면 반대로 Observabel 객체는 값의 변경점에서 이벤트를 발생시켜서 TOP CD까지 변경을 전파한다. 따라서 Angular는 ROOT에서 TOP으로 가는 길목에 있는 CD만 수행하면 되는 것이다. 이때 OnPush전략을 함께 사용하면 Observable 객체의 레퍼런스는 변경되지 않기 때문에, 이벤트 컴포넌트 Subtree의 CD가 생략 된다. 하위 컴포넌트들도 함께 업데이트 해야한다면 디텍터의 markForCheck()라는 API를 이용하여 Subtree까지 CD를 수행 할 수 있다.
http://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html
마치며
AngularJs 1.x에서 Angular 2로 넘어오면서 가장 관심이 갔던 부분은 성능이었다.
Change Detection에 대해서 자세히 알아보기 전까진 ReactJS의 Virtual DOM과 비슷한 방식이라 생각했었는데, 깊이 들여다보니 Framewrok로서 장점을 잘 이용한 것 같다. 성능 저하와 복잡도 상승의 주 원인이던 양방향 바인딩을 없애고, Zone.js를 도입 함으로써 라이브러리 사용에 비해 자유롭지 못한 Framework의 단점을 보완하려 한 부분도 흥미로웠다. 들여다 볼수록 매력있는 Angular지만 제대로 사용하지 않으면 이러한 장점들의 존재조차 모르고 사용하게 될것이란 게 두렵기도 하다.
참조
Change Detection In Angular : https://vsavkin.com/change-detection-in-angular-2-4f216b855d4c
Change Detection : https://angular-2-training-book.rangle.io/handout/change-detection/
Angular Change Detection Explained : https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html
OnPush 동작 방식 이해 : https://jongmoon.github.io/blog/2017/02/13/ng-onpush-mechanism/
자바스크립트 프레임워크 소개 2 : Angulr2 - http://meetup.toast.com/posts/98
'ANGULAR' 카테고리의 다른 글
IntelliJ 에서 Angular 2 프로젝트 시작하기 (499) | 2017.05.16 |
---|