티스토리 뷰
[번역] 아직도 NgZone이 단순하게 Angular의 변화감지(Change Detection)를 위해서만 필요하다고 생각하시나요?
norux 2018. 4. 23. 17:57[번역] 아직도 NgZone이 단순하게 Angular의 변화감지(Change Detection)를 위해서만 필요하다고 생각하시나요?
부제 - NgZone의 구현방법과 사용법
본 포스팅은 Do you still think that NgZone (zone.js) is required for change detection in Angular? 을 번역한 글입니다.
많은 의역이 포함되어 있을 수 있습니다. :)
대부분의 포스팅에서 Zone
(zone.js
)와 NgZone
이 Angular의 변화 감지Change detection과 강한 연관성이 있다고 말합니다. 분명 Angular의 변화 감지 기술에 Zone
이 관련되어있는 것은 사실이지만, 기술적으로 부분 집합적인 관계는 아닙니다. 물론 Zone
과 NgZone
은 비동기 오퍼레이션의 결과로 변화 감지Change Detection를 자동으로 발생시키는데 사용됩니다. 하지만 변화 감지는 별도의 메커니즘이기 때문에, Zone
과 NgZone
없이도 구현할 수 있습니다. 첫 번째 챕터에서, Zone
을 사용하지 않는 Angular를 설명하겠습니다. 그리고 이어지는 두 번째 챕터에서는, Angular와 Zone
이 어떻게 NgZone
을 통해 상호 작동하는지 알아보겠습니다. 마지막으로는 Google API Client Library(gapi)같은 일부 서드 파티 라이브러리에서 자동 변화 감지가 동작하지 않는 이유에 대해서 알아보겠습니다.
저는 Angular의 변화감지에 대해 깊이 알아보는 포스팅을 여러개 썼습니다. 그리고 이 글로 큰 그림을 완성합니다. 만약 여러분이 변화 감지가 어떻게 동작하는지에 대해 개괄적인 내용을 보고 싶으시다면, 여기(These 5 articles will make you an Angular Change Detection expert)에 나와있는 모든 글을 읽으시는 것을 추천드립니다.
Zone 없는 Angular
Zone 없이 Angular가 동작할 수 있다는 것을 설명하기 위해서, 저는 처음에는 아무 동작도 하지 않는 가짜 Zone(mock zone) 객체를 만드려고 했었습니다. 하지만 Angular 5 버전에서는 쉽게 Zone 없는 Angular를 사용할 수 있도록 해줍니다. Angular5에서는 아무 일도 하지 않는 noop zone 을 사용하는 방법을 제공합니다. 자, 그럼 Zone
의 의존성을 제거하는 작업부터 해봅시다. 데모를 위해 stackblitz(웹IDE) 를 사용했습니다. 그리고 stackblitz가 Angular-CLI를 사용하기 때문에, polyfils.ts
파일에 있는 다음 import 를 제거합니다.
역자주: 스택블리츠를 사용하지 않고, 로컬에서 angular-cli를 사용하시면 똑같이 데모를 만들어 보실 수 있습니다. angular-cli의 사용법은 angular-cli를 이용한 Angular2 시작하기 (Quick Start) 를 참고해주세요. >_< (혼틈 셀프 홍보)
/* Zone JS is required by Angular itself. */
import 'zone.js/dist/zone'; // Included with Angular CLI.
Angular에서 noop zone을 사용하도록 설정하는 방법은 아래와 같습니다.
platformBrowserDynamic()
.bootstrapModule(AppModule, {
ngZone: 'noop'
});
이 상태에서 애플리케이션을 실행하면, 변화 감지(Change Detection)이 완전히 동작해서, DOM에 있는 name
컴포넌트 속성을 랜더링 하는 것을 볼 수 있습니다.
이제, setTimeout
함수를 이용해서 속성을 변경하도록 해봅시다.
export class AppComponent {
name = 'Angular 4';
constructor() {
setTimeout(() => {
this.name = 'updated';
}, 1000);
}
이번에는 변화가 업데이트가 되지 않는 것을 볼 수 있습니다. ngZone
을 사용하지 않기 때문에 그렇습니다. NgZone
이 수행하는 자동 변화 감지가 일어나지 않는 것입니다. 그래도 변화감지를 수동으로 발생시킨다면, 이 코드는 잘 동작하게 됩니다. ApplicationRef 를 인젝션하여, tick
메소드를 발생시키면 됩니다.
export class AppComponent {
name = 'Angular 4';
constructor(app: ApplicationRef) {
setTimeout(()=>{
this.name = 'updated';
app.tick();
}, 1000);
}
이제 성공적으로 랜더링을 업데이트하는 것을 볼 수 있습니다.
이 파트를 요약하자면, 위 설명의 포인트는 Zone
과 NgZone
이 변화 감지의 일부분이 전혀 아니라는 것입니다. 이들은 app.tick()
을 자동으로 호출하는 자동 변화 감지를 발생시키기 위한 매우 편리한 매커니즘일뿐입니다. 잠시 후에 이 매커니즘에 대해 알아보겠습니다.
NgZone이 Zone을 사용하는 방법
저의 Zone에 대한 이전 포스팅에서 Zone
이 제공하는 API와 내부 동작에 대해 알아보았습니다. 여기서 저는 zone을 복제하는 핵심 개념과 각각의 존에서 태스크를 실행하는 것에 대해서 설명했습니다. 이 개념들이 여기서 언급됩니다.
또 저는 Zone
이 제공하는 두 가지 능력에 대해서도 설명했습니다. 문맥 전파(Context propagation)에 대한 것과 중요한 비동기 태스크를 추적하는 것에 대한 것이었습니다. Angular는 태스크를 추적하는 매커니즘에 강하게 의존하는 NgZone 클래스를 구현합니다. NgZone
은 단지 자식 존을 복제하는 것에 대한 래퍼(wrapper)입니다.
function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) {
zone._inner = zone._inner.fork({
name: 'angular',
...
복제된 존은 _inner
속성에 저장됩니다. 그리고 보통 Angular zone으로써 참조합니다. 이 존은 NgZone.run()
을 실행 할 때, 사용되는 존입니다.
run(fn, applyThis, applyArgs) {
return this._inner.run(fn, applyThis, applyArgs);
}
Angular zone을 복제한 순간에 현재 존은 _outer
속성에 저장됩니다. 그리고 이 존은 NgZone.runOutsideAngular()
메소드를 실행할 때, 사용되는 존입니다.
runOutsideAngular(fn) {
return this._outer.run(fn);
}
이 메소드는 성능에 영향을 끼칠 수 있는 작업을 실행할 때 종종 사용됩니다. Angular zone의 밖에서 실행되게 함으로써, 변화감지가 발생하는 것을 피할 수 있습니다.
NgZone은 중요한 마이크로 태스크나 매크로 태스크가 있는지 여부를 알려주는 isStable
이라는 속성을 가지고 있습니다. 또한 NgZone은 다음 4개의 이벤트를 정의합니다.
이벤트 | 설명 |
---|---|
onUnstable | Angular Zone에 진입할 때 발생합니다. 이 이벤트는 작업 턴(VM Turn)의 처음 한 번만 발생합니다. |
onMicrotaskEmpty | 현재 작업 큐에 더 이상 마이크로 태스크가 존재하지 않을 때 발생합니다. 이 이벤트는 Angular가 마이크로 태스크가 큐에 들어 온 변화 감지를 수행하는 것을 도와줍니다. 따라서 이 이벤트는 각 작업 턴(VM Turn)마다 여러 번 발생할 수 있습니다. |
onStable | 마지막 onMicrotaskEmtpy 실행되어, 더 이상 마이크로 태스크가 존재하지 않을 때 발생합니다. 이것은 곧 작업 턴(VM Turn)을 종료할 것임을 의미합니다. 이 이벤트는 한 번만 발생합니다. |
onError | 에러가 있을 때, 발생합니다. |
Angular는 변화 감지를 자동으로 발생시키기 위해서 ApplicationRef 내부에서 onMicrotaskEmpty
이벤트를 사용합니다.
this._zone.onMicrotaskEmpty.subscribe(
{next: () => { this._zone.run(() => { this.tick(); }); }});
아직 이전 섹션에서 배운게 기억난다면, tick()
메소드는 어플리케이션의 변화감지를 실행할 때 사용하는 메소드라고 알고 계실 겁니다.
NgZone이 onMicrotaskEmpty 이벤트를 구현하는 방법
자, 이제 NgZone
이 onMicrotaskEmpty
이벤트를 구현하는 방법에 대해서 알아보겠습니다. 이 이벤트는 checkStable 함수에서 발생합니다.
function checkStable(zone: NgZonePrivate) {
if (zone._nesting == 0 && !zone.hasPendingMicrotasks && !zone.isStable) {
try {
zone._nesting++;
zone.onMicrotaskEmpty.emit(null); // <----------- 바로 여기!!!!!!!!!!!!
그리고 이 함수는 일반적으로 다음 세 가지 존의 후킹 메소드에서 호출됩니다.
- onHasTask
- onInvokeTask
- onInvoke
역자주: 저자의 이전 포스팅에서 설명한 내용입니다. 저 역시 번역한 글이 있습니다.
onHasTask는 microTask, macroTask, eventTask, change 라는 4개의 상태를 전달해줬습니다. 이 상태로 큐 안에 태스크가 존재하는지 확인할 수 있었지만, 단지 '큐가 비었는지, 아닌지'를 판단했기 때문에 하나의 작업턴에서 여러 개의 태스크가 실행되는 것을 알 수는 없었습니다.
onInvokeTask는 위 개별 태스크를 추적하지 못하는 단점을 보완할 수 있는 함수로써, setTimeout(callback) 같은 비동기 함수에서 인자로 넘겨받은 콜백함수가 실행될 때, 발생했습니다.
마지막으로 onInvoke는 존에 진입할 때, 이벤트가 발생하는 것이었습니다. '존에 진입한다' 라는 것은,
z.run()
을 호출하거나 존 내부에서 특정 함수를 호출할 때 였습니다.
Zone에 대해 설명한 이전 포스팅에서 설명한 것처럼, 마지막 두 개의 훅은 마이크로태스크 큐에 변화가 있을 때 발생합니다. 따라서 Angular는 훅이 동작할 때마다, stable
검사를 수행해야만 합니다. 또한 onHasTask
훅도 전체의 큐가 변화하는 것을 추적하므로 검사를 수행할 수 있습니다.
흔한 함정
변화 감지에 관련되어 스택오버플로우에 올라오는 가장 빈번한 질문중 하나는, 왜 일부 서드파티 라이브러리를 사용할 때, 컴포넌트가 변화를 감지하지 못하는지 입니다. 예를 들어 이런 질문들이 있습니다. 이러한 문제에 대해 가장 일반적인 해결책은 Angular zone 내부에서 콜백을 실행하도록 하는 것입니다.
gapi.load('auth2', () => {
zone.run(() => {
...
하지만, 왜 존이 훅 중 하나의 통지를 받는 요청을 등록하지 못하는지 궁금합니다. 그리고 NgZone
자동으로 변화감지를 발생시키지 못하는 것에 대한 것입니다.
이걸 이해하기위해서, 저는 gapi
의 minified된 소스를 파보았고, gapi
가 네트워크 요청을 받기 위해 JSONP를 사용한다는 것을 발견했습니다. 이 접근법은 존에 의해 패치/추적되는 XMLHttpReqeust나 Fetch API 같이 일반적인 Ajax를 사용하는 방법이 아닙니다. 대신에 URL을 가지는 script
태그를 생성하고, 서버로부터 가져오는 데이터를 담는 스크립트가 요청 될 때 발생하는 전역 콜백을 정의합니다. 이것은 존에 의해 패치되거나 탐지될 수 없습니다. 따라서 Angular는 이 기술을 사용한 요청에 대해 까맣게 잊어버리게 되는 것입니다.
여기에 gapi 최소화 버전에서 추출한 코드 스니펫이 있습니다.
Ja = function(a) {
var b = L.createElement(Z);
b.setAttribute(“src”, a);
a = Ia();
null !== a && b.setAttribute(“nonce”, a);
b.async = “true”;
(a = L.getElementsByTagName(Z)[0]) ?
a.parentNode.insertBefore(b, a) :
(L.head || L.body || L.documentElement).appendChild(b)
}
z
변수는 script
와 똑같고, 파라미터인 a
는 요청 URL을 가지고 있습니다.
https://apis.google.com/_.../cb=gapi.loaded_0
URL의 마지막 세그먼트는 gapi.loaded_0
이라는 전역 콜백입니다.
typeof gapi.loaded_0
"function"
읽어주셔서 감사합니다. 이 포스티잉 좋았다면 박수를 눌러주시구요. 더 많은 이야기가 읽고 싶으시면, 제 Twitter 나 Midium을 팔로우 해주세요.
참고
'Javascript&Typescript > Angular2' 카테고리의 다른 글
[번역] Angular에서 Zones(zone.js)의 역할 (0) | 2018.04.15 |
---|---|
[번역글] Angular에서 Rx.js의 Observable 관리하기 (0) | 2018.04.09 |
angular-cli를 이용한 Angular2 시작하기 (Quick Start) (2) | 2017.05.25 |
- Total
- Today
- Yesterday
- ansi color
- JavaScript
- zone
- C언어
- angular2
- qemu linux arm
- observable
- Zone.js
- NgZone
- vim
- ECMA2015
- typeScript
- Swift
- git 설정
- lua table
- 우분투 16.04
- 챗봇
- 스위프트
- 리눅스 터미널 색상
- itoa
- ZONES
- terminal 색
- 폰트 조정
- Rx.js
- git proxy
- Angular
- QT
- 안시 색상
- 안시 컬러
- 타입스크립트
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |