웹사이트 검색

Angular에서 변경 감지 전략을 사용하는 방법


소개

기본적으로 Angular 2+는 앱에서 변경 사항이 있을 때마다 모든 구성 요소(위에서 아래로)에서 변경 감지를 수행합니다. 사용자 이벤트 또는 네트워크 요청에서 받은 데이터에서 변경이 발생할 수 있습니다.

변경 감지는 성능이 매우 뛰어나지만 앱이 더 복잡해지고 구성 요소의 양이 증가함에 따라 변경 감지는 점점 더 많은 작업을 수행해야 합니다.

한 가지 솔루션은 특정 구성 요소에 대해 OnPush 변경 감지 전략을 사용하는 것입니다. 이렇게 하면 Angular가 새 참조가 전달될 때와 데이터가 변경될 때만 이러한 구성 요소와 해당 하위 트리에서 변경 감지를 실행하도록 지시합니다.

이 문서에서는 ChangeDetectionStrategyChangeDetectorRef에 대해 알아봅니다.

전제 조건

이 기사를 따라 하려면 다음이 필요합니다.

  • Angular 구성 요소에 대한 어느 정도의 지식이 도움이 될 수 있습니다.
  • 또한 이 문서는 RxJS 라이브러리를 참조하며 BehaviorSubjectObservable에 익숙하면 도움이 될 수 있습니다.

ChangeDetectionStrategy 예제 살펴보기

수생 생물 목록을 표시하고 사용자가 목록에 새 생물을 추가할 수 있도록 하는 하위 구성 요소가 있는 샘플 구성 요소를 살펴보겠습니다.

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  aquaticCreatures = ['shark', 'dolphin', 'octopus'];

  addAquaticCreature(newAquaticCreature) {
    this.aquaticCreatures.push(newAquaticCreature);
  }
}

템플릿은 다음과 같습니다.

<input #inputAquaticCreature type="text" placeholder="Enter a new creature">
<button (click)="addAquaticCreature(inputAquaticCreature.value)">Add creature</button>

<app-child [data]="aquaticCreatures"></app-child>

app-child 구성요소는 다음과 유사합니다.

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html'
})
export class ChildComponent {
  @Input() data: string[];
}

그리고 app-child 템플릿은 다음과 유사합니다.

<ul>
  <li *ngFor="let item of data">{{ item }}</li>
</ul>

브라우저에서 애플리케이션을 컴파일하고 방문한 후 shark, dolphinoctopus가 포함된 정렬되지 않은 목록을 관찰해야 합니다.

입력 필드에 수생 생물을 입력하고 생물 추가 버튼을 클릭하면 새 생물이 목록에 추가됩니다.

Angular가 부모 구성 요소에서 데이터가 변경되었음을 감지하면 자식 구성 요소가 업데이트됩니다.

이제 하위 구성 요소의 변경 감지 전략을 OnPush로 설정하겠습니다.

import { Component, Input, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent {
  @Input() data: string[];
}

브라우저에서 애플리케이션을 다시 컴파일하고 방문한 후 shark, dolphinoctopus가 포함된 정렬되지 않은 목록을 관찰해야 합니다.

그러나 새로운 수생 생물을 추가해도 정렬되지 않은 목록에 추가되지 않는 것 같습니다. 새 데이터는 여전히 상위 구성 요소의 aquaticCreatures 배열로 푸시되지만 Angular는 데이터 입력에 대한 새 참조를 인식하지 못하므로 구성 요소에서 변경 감지를 실행하지 않습니다.

데이터 입력에 대한 새 참조를 전달하려면 Array.pushaddAquaticCreature에서 확산 구문(...)으로 바꿀 수 있습니다.

// ...
addAquaticCreature(newAquaticCreature) {
  this.aquaticCreatures = [...this.aquaticCreatures, newAquaticCreature];
}
// ...

이 변형을 사용하면 더 이상 aquaticCreatures 배열을 변경하지 않습니다. 완전히 새로운 배열을 반환하고 있습니다.

다시 컴파일한 후 응용 프로그램이 이전과 같이 작동하는지 관찰해야 합니다. Angular는 data에 대한 새로운 참조를 감지하여 하위 구성 요소에서 변경 감지를 실행했습니다.

이것으로 OnPush 변경 감지 전략을 사용하도록 샘플 부모 및 자식 구성 요소 수정을 마칩니다.

ChangeDetectorRef 예제 살펴보기

OnPush의 변경 감지 전략을 사용하는 경우 무언가가 변경되어야 할 때마다 새 참조를 전달하는 것 외에 ChangeDetectorRef를 사용하여 완전한 제어를 할 수도 있습니다.

ChangeDetectorRef.detectChanges()

예를 들어 데이터를 계속 변경한 다음 새로 고침 버튼이 있는 하위 구성 요소에 버튼을 가질 수 있습니다.

Array.push를 사용하려면 addAquaticCreature를 되돌려야 합니다.

// ...
addAquaticCreature(newAquaticCreature) {
  this.aquaticCreatures.push(newAquaticCreature);
}
// ...

그리고 refresh()를 트리거하는 button 요소를 추가합니다.

<ul>
  <li *ngFor="let item of data">{{ item }}</li>
</ul>

<button (click)="refresh()">Refresh</button>

그런 다음 ChangeDetectorRef를 사용하도록 하위 구성 요소를 수정합니다.

import {
  Component,
  Input,
  ChangeDetectionStrategy,
  ChangeDetectorRef
} from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent {
  @Input() data: string[];

  constructor(private cd: ChangeDetectorRef) {}

  refresh() {
    this.cd.detectChanges();
  }
}

브라우저에서 애플리케이션을 컴파일하고 방문한 후 shark, dolphinoctopus가 포함된 정렬되지 않은 목록을 관찰해야 합니다.

배열에 새 항목을 추가해도 정렬되지 않은 목록은 업데이트되지 않습니다. 그러나 새로 고침 버튼을 누르면 구성 요소에 대한 변경 감지가 실행되고 업데이트가 수행됩니다.

ChangeDetectorRef.markForCheck()

데이터 입력이 실제로 관찰 가능하다고 가정해 보겠습니다.

이 예제에서는 RxJS BehaviorSubject를 사용합니다.

import { Component } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Component({ 
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  aquaticCreatures = new BehaviorSubject(['shark', 'dolphin', 'octopus']);

  addAcquaticCreature((newAquaticCreature) {
    this.aquaticCreatures.next(newAquaticCreature);
  }
}

그리고 자식 구성 요소의 OnInit 후크에서 구독합니다.

여기에서 aquaticCreatures 배열에 수생 생물을 추가합니다.

import {
  Component,
  Input,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  OnInit
} from '@angular/core';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
  @Input() data: Observable<any>;
  aquaticCreatures: string[] = [];

  constructor(private cd: ChangeDetectorRef) {}

  ngOnInit() {
    <^>this.data.subscribe(newAquaticCreature => {
      this.aquaticCreatures = [...this.aquaticCreatures, ...newAquaticCreature];
    });
  }
}

이 코드는 새 데이터가 관찰 가능한 data를 변경하므로 Angular가 변경 감지를 실행하지 않기 때문에 완전하지 않습니다. 해결 방법은 observable을 구독할 때 ChangeDetectorRefmarkForCheck를 호출하는 것입니다.

// ...
ngOnInit() {
  this.data.subscribe(newAquaticCreature => {
    this.aquaticCreatures = [...this.aquaticCreatures, ...newAquaticCreature];
    this.cd.markForCheck();
  });
}
// ...

markForCheck는 이 특정 입력이 변형될 때 변경 감지를 트리거하도록 Angular에 지시합니다.

ChangeDetectorRef.detach() 및 ChangeDetectorRef.reattach()

ChangeDetectorRef로 할 수 있는 또 다른 강력한 기능은 detachreattach 메서드를 사용하여 변경 감지를 수동으로 완전히 분리하고 다시 연결하는 것입니다.

결론

이 문서에서는 ChangeDetectionStrategyChangeDetectorRef를 소개했습니다. 기본적으로 Angular는 모든 구성 요소에서 변경 감지를 수행합니다. ChangeDetectionStrategyChangeDetectorRef는 구성 요소에 적용하여 데이터가 변경될 때와 비교하여 새 참조에 대한 변경 감지를 수행할 수 있습니다.

Angular에 대해 자세히 알아보려면 연습 및 프로그래밍 프로젝트에 대한 Angular 주제 페이지를 확인하세요.