Angular 앱의 성능을 개선하는 방법
게시 됨: 2020-04-10최고의 프론트엔드 프레임워크에 대해 이야기할 때 Angular를 언급하지 않을 수 없습니다. 그러나 그것을 배우고 현명하게 사용하려면 프로그래머의 많은 노력이 필요합니다. 불행히도 Angular에 대한 경험이 없는 개발자가 Angular의 일부 기능을 비효율적으로 사용할 수 있는 위험이 있습니다.
프론트엔드 개발자로서 항상 작업해야 하는 많은 것 중 하나는 앱의 성능입니다. 과거 프로젝트의 상당 부분은 계속해서 확장되고 개발되는 대규모 엔터프라이즈 애플리케이션에 중점을 두었습니다. 프론트엔드 프레임워크는 여기에서 매우 유용하지만 올바르고 합리적으로 사용하는 것이 중요합니다.
Angular 애플리케이션의 성능을 즉시 향상시키는 데 도움이 될 수 있는 가장 인기 있는 성능 향상 전략과 팁의 빠른 목록을 준비했습니다. 여기에 있는 모든 힌트는 버전 8의 Angular에 적용됩니다.
ChangeDetectionStrategy 및 ChangeDetectorRef
변경 감지(CD) 는 데이터 변경을 감지하고 자동으로 이에 대응하는 Angular의 메커니즘입니다. 표준 애플리케이션 상태 변경의 기본 종류를 나열할 수 있습니다.
- 이벤트
- HTTP 요청
- 타이머
이는 비동기식 상호 작용입니다. 질문은 다음과 같습니다. Angular는 일부 상호 작용(예: 클릭, 간격, http 요청)이 발생하고 애플리케이션 상태를 업데이트해야 한다는 것을 어떻게 알 수 있습니까?
대답은 기본적으로 비동기식 상호 작용을 추적하기 위한 복잡한 시스템인 ngZone 입니다. 모든 작업이 ngZone에 의해 등록된 경우 Angular는 일부 변경 사항에 대응할 때를 알고 있습니다. 그러나 정확히 무엇이 변경되었는지 알지 못하고 모든 구성 요소를 1차적으로 확인하는 변경 감지 메커니즘을 시작합니다.
Angular 앱의 각 구성 요소에는 변경 감지가 시작되었을 때 이 구성 요소가 작동하는 방식을 정의하는 자체 변경 감지기가 있습니다. Angular가 변경 감지를 시작하면 모든 단일 구성 요소가 확인되고 해당 보기(DOM)가 기본적으로 다시 렌더링될 수 있습니다.
ChangeDetectionStrategy.OnPush를 사용하여 이를 방지할 수 있습니다.
@요소({ 선택기: 'foobar', templateUrl: './foobar.component.html', styleUrls: ['./foobar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush })
위의 예제 코드에서 볼 수 있듯이 구성 요소의 데코레이터에 추가 매개 변수를 추가해야 합니다. 그러나 이 새로운 변경 감지 전략은 실제로 어떻게 작동합니까?
이 전략은 특정 구성 요소가 @Inputs()에만 의존한다고 Angular에 알려줍니다. 또한 모든 구성 요소 @Inputs()는 변경할 수 없는 객체처럼 작동합니다(예: 참조를 변경하지 않고 객체의 @Input()에서 속성만 변경할 때 이 구성 요소는 검사되지 않습니다). 불필요한 검사가 많이 생략되고 앱 성능이 향상되어야 함을 의미합니다.
ChangeDetectionStrategy.OnPush가 있는 구성 요소는 다음 경우에만 확인됩니다.
- @Input() 참조가 변경됩니다.
- 이벤트는 구성 요소의 템플릿 또는 해당 하위 요소 중 하나에서 트리거됩니다.
- 구성 요소에서 관찰 가능은 이벤트를 트리거합니다.
- CD 는 ChangeDetectorRef 서비스를 사용하여 수동으로 실행됩니다.
- async 파이프 는 뷰에서 사용됩니다(비동기 파이프는 변경 사항을 확인할 구성 요소를 표시합니다. 소스 스트림이 새 값을 내보낼 때 이 구성 요소가 확인됩니다)
위의 어느 것도 발생하지 않는 경우 특정 구성 요소 내에서 ChangeDetectionStrategy.OnPush를 사용하면 CD 실행 후 구성 요소와 모든 중첩 구성 요소가 확인되지 않습니다.
다행히도 ChangeDetectorRef 서비스를 사용하여 데이터 변경에 대한 반응을 완전히 제어할 수 있습니다. 타임아웃, 요청, 구독 콜백 내부에 ChangeDetectionStrategy.OnPush를 사용하면 실제로 필요한 경우 CD를 수동으로 실행해야 한다는 점을 기억해야 합니다.
카운터 = 0; 생성자(비공개 changeDetectorRef: ChangeDetectorRef) {} ngOnInit() { setTimeout(() => { this.counter += 1000; this.changeDetectorRef.detectChanges(); }, 1000); }
위에서 볼 수 있듯이 timeout 함수 내에서 this.changeDetectorRef.detectChanges()를 호출하여 수동으로 CD 를 강제 실행할 수 있습니다. 카운터가 어떤 식으로든 템플릿 내부에서 사용되면 해당 값이 새로 고쳐집니다.
이 섹션의 마지막 팁은 특정 구성 요소에 대해 CD 를 영구적으로 비활성화하는 것입니다. 정적 구성 요소가 있고 그 상태가 변경되어서는 안 된다고 확신하는 경우 CD 를 영구적으로 비활성화할 수 있습니다.
this.changeDetectorRef.detach()
이 코드는 데이터 새로 고침을 비활성화하기 전에 뷰가 올바르게 렌더링되었는지 확인하기 위해 ngAfterViewInit() 또는 ngAfterViewChecked() 수명 주기 메서드 내에서 실행되어야 합니다. 이 구성 요소는 detectChanges()를 수동으로 트리거하지 않는 한 CD 중에 더 이상 확인되지 않습니다.
템플릿의 함수 호출 및 getter
템플릿 내에서 함수 호출을 사용하면 변경 감지기가 실행될 때마다 이 함수가 실행됩니다. getters에서도 동일한 상황이 발생합니다. 가능하다면 우리는 이것을 피하려고 노력해야 합니다. 대부분의 경우 CD 를 실행할 때마다 구성 요소의 템플릿 내에서 기능을 실행할 필요가 없습니다. 그 대신 순수한 파이프를 사용할 수 있습니다.
순수 파이프
순수 파이프 는 부작용 없이 입력에만 의존하는 출력을 가진 파이프의 일종입니다. 운 좋게도 Angular의 모든 파이프는 기본적으로 순수합니다.
@파이프({ 이름: '대문자', 순수: 사실 })
그러나 왜 우리는 pure: false와 함께 파이프를 사용하지 말아야 할까요? 대답은 다시 변경 감지입니다. 순수하지 않은 파이프는 모든 CD 실행에서 실행되며, 이는 대부분의 경우 필요하지 않으며 앱의 성능을 저하시킵니다. 다음은 순수 파이프로 변경할 수 있는 함수의 예입니다.
transform(값: 문자열, 제한 = 60, 줄임표 = '...') { if (!값 || 값.길이 <= 제한) { 반환 값; } const numberOfVisibleCharacters = value.substr(0, 제한).lastIndexOf(' '); return `${value.substr(0, numberOfVisibleCharacters)}${ellipsis}`; }
그리고 보기를 보자:
<p class="description">자르기(텍스트, 30)</p>
위의 코드는 순수한 기능을 나타냅니다. 부작용이 없으며 출력은 입력에만 의존합니다. 이 경우 이 함수를 순수 파이프 로 간단히 대체할 수 있습니다.
@파이프({ 이름: '잘라내기', 순수한: 사실 }) 내보내기 클래스 TruncatePipe 구현 PipeTransform { transform(값: 문자열, 제한 = 60, 줄임표 = '...') { ... } }
마지막으로 이 보기에서 변경 감지 와 독립적으로 텍스트가 변경된 경우에만 실행되는 코드를 얻습니다.
<p class="description">{{ 텍스트 | 자르기: 30 }}</p>
지연 로딩 및 사전 로딩 모듈
애플리케이션에 둘 이상의 페이지가 있는 경우 프로젝트의 각 논리적 부분, 특히 지연 로드 모듈 에 대한 모듈을 만드는 것을 확실히 고려해야 합니다. 간단한 Angular 라우터 코드를 살펴보겠습니다.
const 경로: 경로 = [ { 길: '', 구성 요소: 홈 구성 요소 }, { 경로: '푸', loadChildren: ()=> import("./foo/foo.module").then(m => m.FooModule) }, { 경로: '바', loadChildren: ()=> import("./bar/bar.module").then(m => m.BarModule) } ] @NgModule({ 내보내기: [라우터 모듈], 가져오기: [RouterModule.forRoot(경로)] }) 클래스 AppRoutingModule {}
위의 예에서 모든 자산이 포함된 fooModule은 사용자가 특정 경로 (foo 또는 bar)를 입력하려고 할 때만 로드됨을 알 수 있습니다. Angular는 이 모듈 에 대한 별도의 청크 도 생성합니다. 지연 로드 는 초기 로드 를 줄입니다.

추가 최적화를 수행할 수 있습니다. 백그라운드에서 앱 로딩 모듈 을 만들고 싶다고 가정해 봅시다. 이 경우 preloadingStrategy를 사용할 수 있습니다. Angular에는 기본적으로 두 가지 유형의 preloadingStrategy가 있습니다.
- 미리 로드하지 않음
- 모든 모듈 미리 로드
위의 코드에서는 기본적으로 NoPreloading 전략이 사용됩니다. 앱은 사용자 요청에 의해 특정 모듈을 로드하기 시작합니다(사용자가 특정 경로를 보고 싶을 때). 라우터에 몇 가지 추가 구성을 추가하여 이를 변경할 수 있습니다.
@NgModule({ 내보내기: [라우터 모듈], 가져오기: [RouterModule.forRoot(경로, { preloading전략: PreloadAllModules }] }) 클래스 AppRoutingModule {}
이 구성을 사용하면 가능한 한 빨리 현재 경로가 표시되고 그 후에 애플리케이션은 백그라운드에서 다른 모듈을 로드하려고 시도합니다. 똑똑하지, 그렇지? 하지만 그게 다가 아닙니다. 이 솔루션이 우리의 요구에 맞지 않는 경우 우리는 단순히 우리 자신의 맞춤형 전략 을 작성할 수 있습니다.
BarModule과 같은 선택된 모듈만 미리 로드한다고 가정해 보겠습니다. 데이터 필드에 추가 필드를 추가하여 이를 나타냅니다.
const 경로: 경로 = [ { 길: '', 구성 요소: 홈 구성 요소 데이터: { 사전 로드: 거짓 } }, { 경로: '푸', loadChildren: ()=> import("./foo/foo.module").then(m => m.FooModule), 데이터: { 사전 로드: 거짓 } }, { 경로: '바', loadChildren: ()=> import("./bar/bar.module").then(m => m.BarModule), 데이터: { 사전 로드: true } } ]
그런 다음 사용자 정의 사전 로드 기능을 작성해야 합니다.
@주사가능() 내보내기 클래스 CustomPreloadingStrategy 구현 PreloadingStrategy { preload(경로: 경로, 로드: () => Observable<any>): Observable<any> { route.data && route.data.preload 반환? load() : of(null); } }
그리고 preloadingStrategy로 설정합니다.
@NgModule({ 내보내기: [라우터 모듈], 가져오기: [RouterModule.forRoot(경로, { preloadingStrategy: CustomPreloadingStrategy }] }) 클래스 AppRoutingModule {}
지금은 param { data: { preload: true } }가 있는 경로 만 미리 로드됩니다. 나머지 경로 는 NoPreloading이 설정된 것처럼 작동합니다.
사용자 정의 preloadingStrategy는 @Injectable()이므로 필요한 경우 내부에 일부 서비스를 주입하고 다른 방식으로 preloadingStrategy를 사용자 정의할 수 있음을 의미합니다.
브라우저의 개발자 도구를 사용하여 preloadingStrategy를 사용하거나 사용하지 않고 동일한 초기 로드 시간 을 통해 성능 향상을 조사할 수 있습니다. 또한 네트워크 탭을 보면 다른 경로에 대한 청크가 백그라운드에서 로드되고 있는 반면 사용자는 지연 없이 현재 페이지를 볼 수 있습니다.
trackBy 함수
대부분의 Angular 앱은 *ngFor를 사용하여 템플릿 내부에 나열된 항목을 반복한다고 가정할 수 있습니다. 반복 목록도 편집할 수 있는 경우 trackBy는 반드시 있어야 합니다.
<울> <tr *ngFor="제품의 제품; trackBy: trackByProductId"> <td>{{ product.title }}</td> </tr> </ul> trackByProductId(인덱스: 번호, 제품: 제품) { 반환 product.id; }
trackBy 함수를 사용하여 Angular는 컬렉션의 어떤 요소가 변경되었는지(지정된 식별자에 따라) 추적하고 이러한 특정 요소만 다시 렌더링할 수 있습니다. trackBy를 생략하면 전체 목록이 다시 로드되며 이는 DOM에서 매우 리소스 집약적인 작업이 될 수 있습니다.
AOT(Ahead-of-Time) 컴파일
Angular 문서에 관하여:
" (...) Angular에서 제공하는 구성 요소 및 템플릿은 브라우저에서 직접 이해할 수 없습니다. Angular 응용 프로그램은 브라우저에서 실행되기 전에 컴파일 프로세스가 필요합니다. "
Angular는 두 가지 유형의 컴파일을 제공합니다.
- Just-in-Time (JIT) – 런타임에 브라우저에서 앱을 컴파일합니다.
- AOT( Ahead-of-Time ) – 빌드 시 앱 컴파일
개발 용도의 경우 JIT 컴파일은 개발자 요구 사항을 충족해야 합니다. 그럼에도 불구하고 프로덕션 빌드의 경우 반드시 AOT 를 사용해야 합니다. angular.json 파일 내부의 aot 플래그가 true로 설정되어 있는지 확인해야 합니다. 이러한 솔루션의 가장 중요한 이점으로는 더 빠른 렌더링, 더 적은 비동기식 요청, 더 작은 프레임워크 다운로드 크기 및 향상된 보안이 있습니다.
요약
응용 프로그램의 성능은 프로젝트의 개발 및 유지 관리 부분에서 염두에 두어야 하는 것입니다. 그러나 가능한 솔루션을 스스로 찾는 것은 시간과 노력이 많이 소요될 수 있습니다. 이러한 일반적으로 저지르는 실수를 확인하고 개발 과정에서 염두에 두는 것은 Angular 앱의 성능을 즉시 개선하는 데 도움이 될 뿐만 아니라 미래의 실수를 피하는 데 도움이 됩니다.
Angular 프로젝트에서 Miquido를 신뢰하십시오
문의하기Miquido로 앱을 개발하고 싶으십니까?
Angular 앱으로 비즈니스에 활력을 불어넣고 싶으신가요? 당사에 연락하여 Angular 앱 개발 서비스를 선택하십시오.