모범 사례
성능 관리

컴포넌트 하위 트리 건너뛰기

JavaScript는 기본적으로 여러 컴포넌트에서 참조할 수 있는 가변 데이터 구조를 사용합니다. Angular는 데이터 구조의 최신 상태가 DOM에 반영되도록 전체 컴포넌트 트리에서 변경 감지를 실행합니다.

대부분의 애플리케이션에서는 변경 감지 속도가 충분히 빠릅니다. 하지만 컴포넌트 트리가 특히 큰 애플리케이션에서 전체 변경 감지를 실행하면 성능 문제가 발생할 수 있습니다. 변경 감지가 컴포넌트 트리의 일부에서만 실행되도록 설정하면 이 문제에 대응할 수 있습니다.

OnPush 사용하기

Angular의 기본 변경 감지 전략은 OnPush입니다(v22부터). 이 전략에서는 다음 경우에만 컴포넌트 하위 트리의 변경 감지를 실행하도록 Angular에 지시합니다:

  • 템플릿 바인딩의 결과로 하위 트리의 루트 컴포넌트가 새 입력값을 받는 경우입니다. Angular는 입력의 현재 값과 이전 값을 Object.is로 비교합니다.
  • Angular가 하위 트리의 루트 컴포넌트나 자식 컴포넌트에서 (이벤트 바인딩, 출력 바인딩, @HostListener 등을 통해) 이벤트를 처리하는 경우입니다. 이때 자식 컴포넌트의 OnPush 변경 감지 사용 여부는 관계없습니다.

일반적인 변경 감지 사례

이 섹션에서는 여러 일반적인 변경 감지 사례를 통해 Angular의 동작을 살펴봅니다.

Eager 변경 감지 컴포넌트에서 이벤트를 처리하는 경우

Angular가 Eager 전략을 사용하는 컴포넌트에서 이벤트를 처리하면 전체 컴포넌트 트리에서 변경 감지를 실행합니다. 이때 루트가 OnPush를 사용하고 새 입력값을 받지 않은 하위 컴포넌트 트리는 건너뜁니다.

예를 들어 MainComponent의 변경 감지 전략을 OnPush로 설정한 뒤, 사용자가 MainComponent를 루트로 하는 하위 트리 바깥의 컴포넌트와 상호작용한다고 해 봅시다. MainComponent가 새 입력값을 받지 않았다면 Angular는 다음 다이어그램의 분홍색 컴포넌트(AppComponent, HeaderComponent, SearchComponent, ButtonComponent)만 검사합니다:

AppComponent

HeaderComponent

MainComponent (OnPush)

SearchComponent

ButtonComponent

LoginComponent (OnPush)

DetailsComponent

Event

OnPush 컴포넌트에서 이벤트를 처리하는 경우

Angular가 OnPush 전략을 사용하는 컴포넌트에서 이벤트를 처리하면 전체 컴포넌트 트리에서 변경 감지를 실행합니다. 이벤트를 처리한 컴포넌트 바깥에 있고, 루트가 OnPush를 사용하며 새 입력값을 받지 않은 하위 컴포넌트 트리는 검사하지 않습니다.

예를 들어 Angular가 MainComponent에서 이벤트를 처리하면 전체 컴포넌트 트리에서 변경 감지를 실행합니다. 이때 LoginComponent는 OnPush를 사용하고 이벤트도 그 범위 바깥에서 발생했으므로, 이를 루트로 하는 하위 트리는 검사하지 않습니다.

AppComponent

HeaderComponent

MainComponent (OnPush)

SearchComponent

ButtonComponent

LoginComponent (OnPush)

DetailsComponent

Event

OnPush 컴포넌트의 하위 컴포넌트에서 이벤트를 처리하는 경우

Angular가 OnPush 컴포넌트에서 이벤트를 처리하면 해당 컴포넌트의 상위 컴포넌트를 포함한 전체 컴포넌트 트리에서 변경 감지를 실행합니다.

예를 들어 다음 다이어그램에서는 OnPush를 사용하는 LoginComponent에서 Angular가 이벤트를 처리합니다. MainComponent도 OnPush를 사용하지만, Angular는 LoginComponent의 부모인 MainComponent를 포함한 전체 컴포넌트 하위 트리에서 변경 감지를 실행합니다. LoginComponent가 MainComponent 뷰의 일부이기 때문에 MainComponent도 검사하는 것입니다.

AppComponent

HeaderComponent

MainComponent (OnPush)

SearchComponent

ButtonComponent

LoginComponent (OnPush)

DetailsComponent

Event

OnPush 컴포넌트에 새 입력값을 전달하는 경우

템플릿 바인딩의 결과로 입력 프로퍼티를 설정하면 Angular는 OnPush 자식 컴포넌트에서 변경 감지를 실행합니다.

예를 들어 다음 다이어그램에서는 AppComponent가 OnPush를 사용하는 MainComponent에 새 입력값을 전달합니다. Angular는 MainComponent에서 변경 감지를 실행하지만, 역시 OnPush를 사용하는 LoginComponent는 새 입력값을 받지 않는 한 변경 감지를 실행하지 않습니다.

AppComponent

HeaderComponent

MainComponent (OnPush)

SearchComponent

ButtonComponent

LoginComponent (OnPush)

DetailsComponent

Parent passes new input to MainComponent

예외적인 경우

  • TypeScript 코드에서 입력 프로퍼티를 수정하는 경우. TypeScript에서 @ViewChild나 @ContentChild 같은 API로 컴포넌트 참조를 얻고 @Input 프로퍼티를 직접 수정하면 Angular는 OnPush 컴포넌트의 변경 감지를 자동으로 실행하지 않습니다. 변경 감지가 필요하다면 컴포넌트에 ChangeDetectorRef를 주입하고 changeDetectorRef.markForCheck()를 호출해 Angular에 변경 감지를 예약하도록 알릴 수 있습니다.
  • 객체 참조를 수정하는 경우. 입력값으로 가변 객체를 받고 참조는 유지한 채 객체를 수정하면 Angular는 변경 감지를 실행하지 않습니다. 입력의 이전 값과 현재 값이 같은 참조를 가리키므로 이는 의도된 동작입니다.