Angular大型应用的高效维护与性能优化指南

2024/12/14 0:03:11

本文主要是介绍Angular大型应用的高效维护与性能优化指南,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一篇文章,你需要的一切都在这里!

感谢你对这篇文章感兴趣。我的名字是帕特里克(Patrik),我是一名中级 Angular 开发者,开发和维护中型和大型的 Angular 项目。在第一部分,我们将讨论如何高效维护大型 Angular 项目。在第二部分,我们将重点讨论性能问题,并提供数据支持的证据,证明这些方法是最好的。让我们开始吧!

熟悉你的工具

我必须强调,很多开发者忽视或不愿意去探索和学习优化工具。这简直是自己给自己挖坑。如果你想让你的应用程序运行得更快,你必须主动去分析其性能。仅仅靠测试是不够的。你需要具体的数字和统计数据来找出问题和瓶颈。

Angular 团队提供了一个强大工具叫做 Angular DevTools。Angular DevTools 是一个浏览器扩展,为 Angular 应用程序提供了调试和性能分析功能。我建议你查看文档,了解更多详情。在开发和维护大型 Angular 应用程序时,了解并使用 DevTools 是非常重要的。当你处理大量组件、异步任务和复杂业务逻辑时,调试和维护工作会更加困难。

TypeScript 和 JSDoc — 双全其美

TypeScript 带来了惊人的特性,并提升了开发体验。在大型 Angular 应用程序中,你可以预期会有大量来自你代码或外部 API 的类型和接口。重要的是要声明你在应用程序中将使用的所有数据类型。类型和接口的名称必须具有实际意义且易于理解。

代码通常是一项长期资产,团队成员可能随时间发生变化。文档确保当开发人员离职或转到其他项目时不会丢失关于代码库的知识。你可以用 JSDoc 描述类型、用途、存在原因及属性。这样你无需直接从代码中理解其功能和意义,而是直接从描述中了解。例如:

    /**  

* 已认证用户可选的订阅类型。  

* 用户可以订阅,但订阅并非强制。  
     */  
    export type Subscriptions = 'basic' | 'advanced' | 'premium' | null  

    /**  

* 认证用户的基本信息。  
     */  
    export type User = {  
     firstName: string;  
     lastName: string;  
     email: string;  
     phone: string | null;  
     avatar: string | null;  
     subscription: Subscriptions  
    };  

    /**  

* 包含认证成功后接收到的所有数据  
     */  
    export type UserAuthentication = {  
     /**  

* 用于访问敏感数据的JWT  
      */  
     accessToken: string;  

     /**  

* 认证用户的基本信息  
      */  
     userData: User;  

     /**  

* 用于在访问令牌过期后刷新访问令牌  
      */  
     refreshToken: string;  
    }

通过在代码中添加 JSDoc,您的 IDE 就会自动加载这些描述。当您将鼠标悬停在代码上时,您就可以看到准备好的文档,如下图所示:

图片 1:鼠标悬停时显示说明:

需要注意的是,TypeScript 并不能确保绝对的类型安全性。为变量分配的类型并不能保证从外部API接收的数据类型一定是正确的。可以将TypeScript视为一个非常聪明的代码检查工具。它帮助你发现类型不匹配、缺少内容或与配置不符的问题。

外部数据验证检查

必须验证接收到的数据,以确保你的应用程序不会出错。用于数据类型验证的库之一是Zod.js,它是一个以 TypeScript 为主的模式声明和验证库。在这里,我用术语“模式”来泛指任何数据类型,从简单的 string 等到复杂的嵌套对象。

在我的项目里,我总是确保彻底检查和验证API响应。后台未验证的更改可能严重影响应用的稳定性,所以我会在这方面特别小心。后端服务配有API文档,列出了所有可用端点,并提供了如何使用它们的明确说明。文档中还包括响应示例,展示了发送给客户端的数据示例。

我发现这些响应示例对于创建与传输数据相匹配的类型和接口超级有帮助。这不仅有助于保持一致性,还使应用程序更加健壮和可靠。我在下面总结了该过程的流程图。

API 数据拉取流程

目录结构和命名约定

组织是保持应用程序良好运行的关键因素。如果找不到某个组件,你可能需要重新审视一下你的代码结构。目录、组件、服务、解析器、类、接口、函数和变量需要合理地安排位置,并且要具有有意义的命名。它们的名字需要清晰且直观,说明它们的用途、代表的意义以及具体功能。

例如,考虑一个注册表单,它在超过四个页面或组件中使用,包含多个输入组件。我们应该怎么给它们命名呢?又该在哪里声明它们?这些组件有多复杂?它们的用途是什么?

    <app-form-container>  
      <app-form-label value="用户名字" />  
      <app-form-text [(username)]="username" />  

      <app-form-label value="邮箱" />  
      <app-form-email [(email)]="email"/>  

      <app-form-label value="生日" />  
      <app-form-date [(date)]="birthday"/>  

      <app-form-label value="密码" />  
      <app-form-password [(password)]="password"/>  

      <app-form-label value="重复密码" />  
      <app-form-password [(password)]="passwordConfirm"/>  

      <app-form-submit (submitClicked)="validateAndRegisterIfPossible()" />  
    </app-form-container>

我们来看看这个问题的视角。

  • 这些是组件。它们应该放在“组件”目录下。
  • 它们带有“form”标签。这些都与表单相关,因此我们将它们放在“组件”目录下的“表单”目录中。
  • 这些组件都与表单相关,所以我们可以把它们放在一个名为“表单组件模块”的模块中。我们可以在需要表单组件的地方导入这个模块。
  • 每个组件都有其特定的功能。“app-form-container”用于包裹其他组件并确保其响应性。“app-form-label”用于显示标签。“app-form-email”设计成在移动设备上以正确的格式和键盘配置来输入电子邮件地址。“app-form-password”设计成在移动设备上以正确的格式和键盘配置来输入密码,并且可以切换密码的显示情况。

比如说:我们有多个页面需要展示数据,这些数据通常会用图表来表示。有各种各样的图表。下面的图展示了一些很好的组件命名和结构:

图2:命名清晰的组件和结构合理

方法装饰器(Method Decorators)

在处理或构建大型 Angular 项目时,bug 是无法避免的。每次你做了改动、修复了 bug 或更新后,你都需要测试一下你的应用。总是可能存在一些没被发现或解决的 bug。

方法装饰器非常实用,你可以用它们来实现很多有趣的功能。其中最简单的一种就是记录函数的执行。

    export class ExampleComponent {   
      @logFunctionExecution  
      protected wrappedFunction() {  
        // 代码...
      }  
    }  

    function logFunctionExecution(target: any, key: string, descriptor: 属性描述符) {  
      console.log(`正在执行 ${key}()`);  
    }

让我们创建一个用于错误处理和日志记录的装饰器函数。我们需要能够捕获方法执行时出现的错误。每次出现错误时,我们都应该在用户界面上显示错误,并将其记录到控制台。

    // 导出函数 logError,用于记录错误信息
    export function logError(methodName: string, page: string) {  
        return function (_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {  
            const originalMethod = descriptor.value;  
            descriptor.value = function (...args: any[]) {  
                try {  
                    const result = originalMethod.apply(this, args);  
                    return result;  
                } catch (error) {  
                    try {  
                        console.log(error);  
                        displayError(error);  
                        sendErrorToServer(serializeError(error, { maxDepth: 5, useToJSON: true }), methodName, page);  
                    } catch (error) {  
                        console.log(error);  
                    }  
                }  
            };  
            return descriptor;  
        }  
    }

现在让我们把装饰器函数应用到类的方法上。

    export class ExampleComponent {  
    @logFunctionExecution("wrappedFunctionOne", "ExampleComponent")  
      protected wrappedFunctionOne() {  
        // 这里是代码实现  
      }  
      @logFunctionExecution("wrappedFunctionTwo", "ExampleComponent")  
      protected wrappedFunctionTwo() {  
        // 具体实现代码  
      }  
      @logFunctionExecution("wrappedFunctionThree", "ExampleComponent")  
      protected wrappedFunctionThree() {  
        // 实现相关代码  
      }  
    }

现在每次有错误时,你都会记录下来并在界面上显示。

注意: 你可能注意到了“ sendErrorToServer ”这个函数。只有在你使用例如 Ionic + Angular 和 Capacitor 将你的网页应用打包成移动端应用时,这个函数才是必要的。在测试过程中,你无法访问控制台日志,因此一种解决方案是使用 serialize-error 包将错误序列化,并将它们发送到后端,这样你就可以在后端跟踪这些错误了。

方法装饰器确实有用,但别过度使用它们。如果你的方法上有8个装饰器,这看起来很乱,也不算好习惯。这里有一篇很好的关于TypeScript装饰器的实用指南。

Angular应用性能了解一下

你知道变化检测是怎么工作的?

变更检测是 Angular 用来检查你的应用程序状态是否发生变化,以及是否需要更新任何 DOM 的过程。它在性能方面起着重要的作用。简单来说,Angular 会自上而下遍历你的组件,寻找变化。Angular 定期运行变更检测机制,确保数据模型中的变化反映在应用程序视图上。变更检测既可以手动触发,也可以异步事件触发,比如用户操作或 XMLHttpRequest 完成。变更检测经过高度优化,性能很好,但如果应用程序频繁运行它,仍然可能造成性能下降。

变更检测策略:OnPush

“OnPush” 变更检测策略使组件更高效,通过减少变更检测检查的频率。当组件配置为“OnPush”模式时,Angular 仅在以下情况下进行变更检测:

  1. 组件的输入属性已经改变了。
  2. 组件通过 ChangeDetectorRef 服务明确要求进行检查。
  3. 处理了由组件或其子组件触发的事件,比如按钮点击。

换句话说,即“OnPush”策略,假设组件的输出仅依赖于其输入和自身的状态。如果这些值没有变化,Angular将跳过该组件及其子组件的检查过程,从而提升性能。


引入 { Component, ChangeDetectionStrategy } from '@angular/core';  

@Component({  
  selector: 'app-example',  
  templateUrl: 'example.component.html',  
  changeDetection: ChangeDetectionStrategy.OnPush,  
})  
导出类 ExampleComponent {  

}

“trackBy” 在 for 循环中

“trackBy” 用于在 *ngFor 指令中唯一标识每个元素,以此提高性能并减少不必要的 DOM 操作。

“trackBy”函数通常与ngFor一起使用,并且在@for循环中是必需的。在使用ngFor时,“trackBy”函数不是必需的,而在_@for_循环中则是必需的。

当你使用 for 循环来渲染项目列表时,Angular 使用默认的对象身份追踪机制。然而,在某些情况下,特别是在处理动态列表或可添加、移除或重新排序的项目时,这种默认机制可能不足以达到最佳性能。trackBy 函数允许你自定义识别和跟踪列表中各个项目的机制。

我们来看一个例子

我们将比较四种不同方法下的变化检测速度:方法一、方法二、方法三和方法四。

  1. 我们将创建一个 NewsService,用来准备 10,000 个条目。我们将提供异步返回这些条目的功能。
    import { Injectable } from '@angular/core';  
    import { NewsMinimalData } from '../Types/news-types';  

    @Injectable({  
      providedIn: 'root'  
    })  
    export class NewsService {  

      private newsList!: Array<NewsMinimalData>;  

      constructor() { }  

      public returnAllNews(): Promise<Array<NewsMinimalData>> {  
        return new Promise((resolve) => {  
          this.createNewsList();  
          setTimeout(() => {  
            resolve(this.newsList);  
          }, 2000);  
        });  
      }  

      private createNewsList() {  
        this.newsList = [];  
        for (let i = 1; i < 10001; i++) {  
          this.newsList.push({  
            id: i,  
            title: "新闻标题#" + i,  
            description: "关于新闻标题# 的简短介绍" + i,  
          });  
        }  
      }  

    }  

2., 创建一个简单的新闻项组件(NewsItem)

    import { 组件, 输入 } from '@angular/core';  
    import { 新闻摘要数据 } from '../../Types/news-types';  

    @Component({  
      selector: 'app-news-item',  
      template: `  
      <div>  
       {{news.id}}, {{news.title}}  
      </div>  
      <div>  
       {{news.description}}  
      </div>  
      `,  
      styles: `  
        :host {  
       display: flex;  
       align-items: center;  
       justify-content: space-between;  
       width: 100%;  
       height: 30px;  
       margin-bottom: 12px;  
      }  
    `,  
      standalone: true  
    })  
    export class 新闻项组件 {  
      @输入({ required: true }) news!: 新闻摘要数据;  
    }

3. 在 AppModule 中导入 FormsModuleNewsItem。如果你是独立使用它们,将它们添加到 imports 数组。

4. 创建一个基本的、功能简单的模板。

    <button (click)="addElement()">添加项</button>  
    <button (click)="removeElement()">移除项</button>  
    <br>  
    <br>  
    <label>数据更改</label>  
    <br>  
    <input placeholder="要更改的新闻ID号" type="number" name="id" id="" [(ngModel)]="idToChange">  
    <br>  
    <input placeholder="新的标题" type="text" name="text" id="" [(ngModel)]="newText">  
    <br>  
    <button (click)="changeData()">更改数据</button>  
    <br>  
    <br>  

    <app-news-item *ngFor="let news of newsList" [news]="news"></app-news-item>  

    <!-- @for (news of newsList; track news.id) {  
    <app-news-item [news]="news"></app-news-item>  
    } -->

5. 在_AppComponent_中添加方法和属性

    import { Component, OnInit } from '@angular/core';  
    import { NewsService } from './Services/news.service';  
    import { NewsMinimalData } from './Types/news-types';  

    @Component({  
      selector: 'app-root',  
      templateUrl: './app-component.html',  
      styles: []  
    })  
    export class AppComponent implements OnInit {  

      protected newsList!: Array<NewsMinimalData>;  
      protected idToChange!: number | null;  
      protected newText!: string | null;  

      constructor(private newsService: NewsService) {  
      }  

      ngOnInit() {  
        this.idToChange = null;  
        this.newText = null;  
        this.newsList = [];  
        this.准备新闻();  
      }  

      protected 更改数据() {  
        let index = this.newsList.findIndex(item => item.id === this.idToChange);  
        this.newsList[index] = { ...this.newsList[index], title: this.newText! };  
      }  

      protected 添加元素() {  
        this.newsList.unshift({  
          id: this.newsList.length + 1,  
          title: 'NEW ITEM',  
          description: 'Description for NEW ITEM'  
        })  
      }  

      protected 跟踪新闻(i: number, e: NewsMinimalData) {  
        return e.id;  
      }  

      protected 移除元素() {  
        this.newsList.shift();  
      }  

      private async 准备新闻() {  
        try {  
          this.newsList = await this.newsService.returnAllNews();  
        } catch (error) {  
          console.log(error);  
        }  
      }  

    }

现在我们已经准备好了。我们将用Angular DevTools提取数据。每个案例都会附带视频。

设备的相关信息:

    .-----------.---------------------------------------------.  
    | 操作系统版本| Ubuntu 23.10 64位, Linux 6.5.0-14-generic |  
    :-----------+---------------------------------------------:  
    | CPU        | AMD Ryzen™ 7 5700U                         |  
    :-----------+---------------------------------------------:  
    | 内存容量   | 16GB DDR4 SDRAM                            |  
    :-----------+---------------------------------------------:  
    | 显卡型号   | NVIDIA GeForce GTX 1650                    |  
    '-----------'---------------------------------------------'  

测试流程:

  1. 证明有10000个元素的存在,
  2. 添加元素,获取变更检测的“花费时间”,
  3. 移除元素,获取变更检测的“花费时间”,
  4. 更改第5个元素的数据内容,获取变更检测的“花费时间”。

没有OnPush和没有启用追踪的ngFor

案例 2: 没有 trackBy 函数的 OnPush 模式的 ngFor,

案例 3:使用带有 OnPush 和 trackBy 函数的 ngFor,

案例4:带有 OnPush 和 trackBy 函数的 @for

我们来提取这些数据。上面的 GIF 图显示的是最坏的情况,这些结果也在下面的表格中。

表格:特定案例的表现情况

根据数据,使用@for和OnPush是检测变化最快的途径。

注意:如果没有使用 trackBy 函数,从列表中移除或更新元素会稍微慢一些,这让我觉得非常有趣。通过多次测试后发现,这种情况其实很少见。移除或更新元素的速度确实比之前慢了一点。

runOutsideAngular 注:此术语用于技术文档中,指在Angular环境之外运行的代码或操作。

runOutsideAngular 方法是一种在 Angular 区之外运行函数的方式。Angular 区是一种机制,用于跟踪应用程序状态的变更并在必要时触发变更检测。这可以通过在 Angular 区之外运行函数来防止变更检测因该函数内的操作而触发。

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

    @Component({  
      selector: 'app-example',  
      template: '<button (click)="runOutsideAngularExample()">在 Angular 区域外运行</button>',  
    })  
    export class ExampleComponent {  
      constructor(private zone: NgZone) {}  

      runOutsideAngularExample() {  
        // 在 Angular 区域外执行操作  
        this.zone.runOutsideAngular(() => {  
          // 这些操作不会触发 Angular 的变更检测  
          console.log('在 Angular 区域外运行');  
        });  
      }  
    }

在上面这个例子中,当按钮被点击时,会执行 runOutsideAngularExample 方法,并且在 zone.runOutsideAngular 函数中的任何操作都不会触发 Angular 的变更检测。这对于那些不需要更新 Angular 应用程序状态或 UI 的任务来说非常有用,有助于提高在某些情况下的性能。

延迟加载

Angular懒加载是一种推迟加载应用程序某些部分的技术。用户访问网站时,应用程序不会加载整个应用程序,而是仅加载初始视图所需的必要组件。额外的模块或组件则根据需要加载,通常由用户的动作触发,比如导航到某个特定路由。

懒加载常与Angular的路由器一起使用。开发者为应用程序的不同部分定义路由,当用户导航到特定路由时,相关的模块会被懒加载。这是通过在路由定义中配置loadChildren属性来实现的,从而实现懒加载。

    const routes: Routes = [  
      { path: '懒加载模块', loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule) },  
      // 等等  
    ];

懒加载可以提高应用程序的初始加载速度,尤其适用于大型应用。通过仅加载当前视图需要的代码,懒加载实现了这一目标。随着用户与应用程序的交互,应用程序的其他部分将异步加载。

不要在模板表达式中使用函数

通常建议不要在模板表达式里直接使用函数,因为这样可能会对应用的性能和维护造成不利影响。

@Component({  
    selector: 'app-test',  
    template: `  
    {{function()}}  
    `,  
})  
// 测试组件类  
class TestComponent {  

    protected function() {  
        // 这里是逻辑代码...  
    }  

}

每当变更检测发生时,函数将被执行。这很容易对性能产生负面影响,因为频繁的变更检测和大量相同的组件会减慢速度并使界面卡顿。如果非要在模板中使用函数,请使用 OnPush 变更检测策略来在一定程度上优化性能。

管道(管子)

管道允许你以清晰和易读的方式直接在模板里表示数据处理。这使模板代码更清晰,更容易维护,让代码更易读。

管道可以在各种组件和模板中重复使用。一旦创建了自定义管道,就可以方便地将其应用到任何需要的地方,促进代码的重复利用。它们在变更检测上非常高效,仅在输入值改变时才计算值。

别避开 RxJS

RxJS,即 JavaScript 的反应式扩展库,提供了一套基于可观察对象(Observable)的响应式编程工具,使得编写异步或基于回调的代码变得更加容易。响应式编程是一种侧重于声明数据依赖关系及其变更传播的编程方法。RxJS 提供了一系列 API 用于处理异步数据流和事件流。其核心概念是可观察对象,它代表了随时间变化的数据或事件流。使用 RxJS 提供的各种操作符,可观察对象可以被操作、组合和转换。

RxJS 通常与 Angular 一起使用。它有助于管理及响应事件、处理异步任务,并简化应用程序中的数据流动过程。利用反应式编程的原则,RxJS 可以使处理复杂异步场景下的代码更加简洁、模块化且易于维护。

防止内存泄漏

在 Angular 应用中,内存泄漏通常发生在对象引用未正确释放时,导致未使用的对象在内存中累积,最常见的内存泄漏原因涉及 Observables。务必始终取消订阅 Observables!

@Component({...}) 
export class VideoUploadComponent 实现 OnInit 接口 { 
  protected videoSubject = new Subject(); 

  ngOnInit() { 
    this.videoSubject.subscribe(videoData => { 
      this.updateInterface(videoData); 
    }); 
  } 

  uploadVideoFile(videoData: any) { 
    // 将 videoData 发送到 videoSubject 
    this.videoSubject.next(videoData); 
  } 

  updateInterface(videoData: any) { 
    // ... 
  } 

  ngOnDestroy() { 
    this.videoSubject.unsubscribe(); 
  } 
}

ngOnDestroy 是有原因的!在组件即将销毁时清理资源并释放内存是最佳时机。内存泄漏会引发性能问题。随着你长时间使用应用,可能会因为垃圾回收器未能释放内存而导致内存耗尽。

服务器端: 渲染

服务器端渲染(SSR)指在服务器而不是浏览器中渲染 Angular 应用程序的过程。传统上,Angular 应用程序使用客户端渲染(CSR),其中渲染和渲染逻辑由浏览器执行。使用 SSR,渲染过程的某些部分或全部会在发送到客户端之前先在服务器上完成。

SSR的主要好处之一是提高了页面的初始加载速度。服务器端渲染初始HTML时,客户端收到预渲染的页面,用户加载速度会更快。

生产设置

在发布或更新应用前,请确保 angular.json 中有生产构建配置。

下面是一个 "@angular-devkit/build-angular:application" 构建器的好的配置示例:

{
     "预算配置": [{  
     "类型": "初始预算",  
     "最大警告": "500kb",  
     "最大错误": "1mb"  
     },  
     {  
     "类型": "任意组件样式",  
     "最大警告": "2kb",  
     "最大错误": "4kb"  
     }  
     ],  
     "polyfills": ["src/polyfills.ts"],  
     "outputHashing": "media",  
     "optimization": true,  
     "extractLicenses": false,  
     "sourceMap": false,  
     "namedChunks": false,  
     "progress": true,  
     "statsJson": true,  
     "prerender": "预渲染",  
     "ssr": "服务端渲染",  
     "server": "src/main.server.ts",  
     "serviceWorker": "ngsw-config.json",  
     "aot": "编译时编译"  
    }

AOT(预编译)是 Angular 中的一种编译过程,它在构建阶段将 Angular 应用程序代码,包括模板和组件,转换为高效的 JavaScript 代码。AOT 的优势包括更快的启动速度、更小的打包体积以及模板错误检查。通过使用 AOT 编译,Angular 应用程序可以实现更好的性能、更快的加载时间和更高的整体效率,因此它被推荐用于生产部署。

optimization’键在‘angular.json’文件中是一个配置选项,用于控制Angular CLI在构建过程中执行各种优化。当该键设置为‘true’时,会在构建过程中触发各种优化,例如,脚本和样式压缩、树摇、死代码移除、关键CSS内联和字体内联。

sourceMap” 这个键应该设置为 false ,因为它仅在调试和开发阶段才需要。

配置选项可以在这里找到:这里:

尽可能减少对外部库的依赖,并谨慎选择。

Angular 提供了多种组件和功能库/包,但并不是所有的性能都很优秀!如果选择使用外部库,请确保做足调研,选择最适合您需求的库。过多的外部库会增加应用的大小,这并不是我们希望看到的。

利用Web Workers的强大能力来

Web Workers 是网页浏览器中的一个特性,它允许在后台并行执行 JavaScript 代码,与主要的浏览器线程隔离。它们让开发者能够运行耗时的任务,比如复杂的计算或数据处理,而不影响用户界面的响应性。Web Workers 通过消息传递机制与主线程通信,促进并发性,从而提高网页应用的性能。

浏览器的主要线程(也称为主线程)负责处理与网页的整体运行和用户界面(UI)相关的各种重要任务。其主要职责包括UI渲染、JS执行、事件处理、布局计算、样式计算、网络请求处理等。

这听起来像是只为一个线程所做的大量工作!我们不妨将计算任务移到Web Worker。这篇文章详细介绍了Web Worker的优势及其使用方法。我将只提供Angular应用的配置步骤:

  1. 创建一个配置文件 tsconfig.worker.json。它应该与 tsconfig.json 文件位于同一目录下。在我这里,我把所有的 worker 文件放在 src/app/Workers 目录下。
{
 "extends": "./tsconfig.json",
 "compilerOptions": {
  "outDir": "输出目录",
  "lib": [
   "库",
   "webworker"
  ],
  "types": [
   "类型声明"
  ]
 },
 "exclude": [
  "排除项"
 ],
 "include": [
  "包含项"
 ],
 "exclude": [],
 "include": [
  "src/app/Workers/*.worker.ts"
 ]
}

Note: There is a redundancy in the exclude and include sections. The correct structure should only have one exclude and one include section. Here is the corrected translation:

{
 "extends": "./tsconfig.json",
 "compilerOptions": {
  "outDir": "输出目录",
  "lib": [
   "库",
   "webworker"
  ],
  "types": [
   "类型声明"
  ]
 },
 "exclude": [
  "排除项"
 ],
 "include": [
  "包含项",
  "src/app/Workers/*.worker.ts"
 ]
}
  1. angular.json 文件中添加你创建的 worker 配置文件。
{  
...  
"projects": {  
  "app": {  
    ...  
    "architect": {  
      ...  
      "build": {  
        "options": {  
           ...  
           "webWorkerTsConfig": "tsconfig.worker.json",  
           ...  
        }  
        ...  
      }  
      ...  
    }  
    ...  
  }  
}  
...  
}

3. 在 app.component.ts 文件中初始化工人。

    @Component({  
    ...  
    })  
    export class AppComponent implements OnInit {  
      protected worker: Worker;  

      constructor() {}  

      ngOnInit() {  
          this.worker = new Worker(new URL(relative_path_to_worker_file, import.meta.url));  

          this.worker.onmessage = (data) => {  
            // 处理数据  
          }  

          this.worker.onerror = (err) => {  
            console.log(err);  
          }  

          this.worker.postMessage(379560249782563);  
      }  

    }
给页面添加动画

你讨厌掉帧现象吗?你讨厌卡顿的动画效果吗?我也一样。接下来我们就来看看如何优化你的动画效果吧!

浏览器渲染,也称为渲染或重绘,是将 HTML、CSS 和其他资源转化为用户屏幕上的视觉效果的过程。这一过程涉及布局、绘制和合成等多个环节,是浏览器渲染流水线中的关键环节。其中最关键的两个环节是布局和绘制。

布局阶段:浏览器根据计算出的样式来确定页面上每个元素的位置和大小。这一步也称为重排。布局阶段决定了元素在屏幕上的排列方式。

绘画步骤:一旦布局确定下来,浏览器将开始绘制阶段。在此步骤中,它会生成实际要显示在屏幕上的像素。这一步涉及到为每个可见元素填充像素,在这一过程中,同时考虑颜色、背景、边框和图像等样式。

合成步骤:将绘制的元素组合在一起,以生成网页的最终视觉效果。这包括按照正确的顺序叠加图层并通过融合产生所需的输出。

不恰当的样式使用、频繁调整布局或大型且复杂的网页会导致性能问题和缓慢的渲染时间。在浏览器渲染和性能优化的背景下,一些CSS样式被归类为“仅合成器”或“低成本”,因为它们不触发布局或绘制。这些样式通常由浏览器的合成器应用,而不引起重新布局或重绘,因此更适合用于动画和过渡效果。

  1. 透明度
  2. 变形
  3. backface-visibility
  4. will-change

_will-change_属性是给浏览器的一个提醒,表明某个属性在未来可能会被动画化或改变。这允许浏览器优化其渲染流程。过度使用它可能会带来负面的性能影响。

让我们来看看使用绝对定位和相对定位之间的区别。我将只添加动画代码,跳过配置部分。我们将在谷歌浏览器的渲染设置中启用页面重绘闪烁。闪烁越少表示性能表现越好。

使用绝对定位的动画:

    import {  
     trigger,  
     transition,  
     style,  
     query,  
     group,  
     animate,  
     AnimationQueryOptions, // AnimationQueryOptions 是一个动画查询选项接口
    } from '@angular/animations';  

    // 导出一个函数 slideToRight 如下:
    export const slideToRight = () => {  
     const optional: AnimationQueryOptions = { optional: true }; // 可选参数设置为 true
     return [  
      // 设置元素相对定位
      style({ position: 'relative' }),  
      // 设置 :enter 和 :leave 的绝对定位
      query(':enter, :leave', [  
       style({  
        position: 'absolute',  
        top: 0,  
        left: 0,  
        width: '100vw',  
        height: '100vh'  
       })  
      ], optional),  
      // 设置 :enter 的绝对定位
      query(':enter', [  
       style({  
        position: 'absolute',  
        top: 0,  
        left: '100%',  
        width: '100vw',  
        height: '100vh'  
       })  
      ], optional),  
      // 分组动画执行
      group([  
       // :leave 的动画执行
       query(':leave', [  
        animate('500ms ease', style({ left: '-100%' })) // 动画持续时间为 500ms,效果为缓和过渡
       ], optional),  
       // :enter 的动画执行
       query(':enter', [  
        animate('500ms ease', style({ left: '0%' })) // 动画持续时间为 500ms,效果为缓和过渡
       ], optional)  
      ]),  
     ];  
    };

使用绝对定位使图像闪烁

现在我们来看使用_transform CSS属性的动画。

    import {  
     trigger,  
     transition,  
     style,  
     query,  
     group,  
     animate,  
     AnimationQueryOptions,  
    } from '@angular/animations';  

    export const slideToRight = () => {  
     const optional: AnimationQueryOptions = { optional: true };  
     return [  
      style({ position: 'relative' }),  
      query(':enter, :leave', [  
       style({  
        position: 'absolute',  
        top: 0,  
        left: 0,  
        width: '100vw',  
        height: '100vh'  
       })  
      ], optional),  
      query(':enter', [  
       style({ transform: 'translateX(100vw)' })  
      ], optional),  
      group([  
       query(':leave', [  
        animate('500ms ease', style({ transform: 'translateX(-100vw)', opacity: '0.75' }))  
       ], optional),  
       query(':enter', [  
        animate('500ms ease', style({ transform: 'translateX(0)', opacity: '1' }))  
       ], optional)  
      ]),  
     ];  
    };

这些动画相当简单,而且页面内容不多。当内容增多且动画变得复杂时,渲染过程会严重影响用户体验。流畅的动画对用户体验至关重要,因此尽量采用仅合成器风格来实现动画。

这就结束了。希望你们从这篇文章中学到了新知识。谢谢大家!



这篇关于Angular大型应用的高效维护与性能优化指南的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程