likes
comments
collection
share

angular视图缓存-模板缓存

作者站长头像
站长
· 阅读数 17

前言

与组件缓存不同,模板缓存关注的是如何缓存已编译的模板片段,以便在需要时快速呈现这些片段,从而减少不必要的重新编译和渲染开销。本文将深入探讨模板缓存的原理、使用方法以及与 ngIf 功能类似但又有所区别的自定义指令。通过阅读本文,您将了解如何最大限度地利用模板缓存来优化您的 Angular 应用程序。

接下来,让我们逐步深入,探究模板缓存的奥秘。从基本概念开始,逐步引导您掌握模板缓存的高级技巧和最佳实践。无论您是 Angular 初学者还是有一定经验的开发者,本文都将为您揭示模板缓存的种种神奇,帮助您写出更出色、更高效的应用代码。

模板缓存原理

模板缓存的核心思想是将编译后的视图工厂函数缓存起来,以便在需要的时候直接使用,而无需再次进行编译。这就意味着,当组件需要重新渲染时,Angular 可以直接使用缓存中的视图工厂函数,从而跳过了编译阶段,大大加快了渲染速度。

自定义缓存指令

@Directive({
  selector: '[appCache]'
})
export class TemplateCacheDirective<T = unknown> {
  private _context: CacheContext<T> = new CacheContext<T>();
  // 条件为ture是将要渲染的模板
  private _thenTemplateRef: TemplateRef<CacheContext<T>>|null = null;
  // 条件为false是将要渲染的模板
  private _elseTemplateRef: TemplateRef<CacheContext<T>>|null = null;
  // 缓存条件为true的视图
  private _thenViewRef: EmbeddedViewRef<CacheContext<T>>|null = null;
  // 缓存条件为false的视图
  private _elseViewRef: EmbeddedViewRef<CacheContext<T>>|null = null;

  @Input()
  set appCache(condition: T) {
    this._context.$implicit = condition;
    this._updateView();
  }

  @Input()
  set appCacheThen(templateRef: TemplateRef<CacheContext<T>>|null) {
    this._thenTemplateRef = templateRef;
    // 如果条件为true将要渲染的模板是动态的情况,则不缓存,这种情况本身很复杂,采用缓存是否合理有待考虑
    this._thenViewRef = null;
    this._updateView();
  }

  @Input()
  set appCacheElse(templateRef: TemplateRef<CacheContext<T>>|null) {
    this._elseTemplateRef = templateRef;
     // 如果条件为false将要渲染的模板是动态的情况,则不缓存,这种情况本身很复杂,采用缓存是否合理有待考虑
    this._elseViewRef = null;
    this._updateView();
  }

  private _updateView() {
    if (this._context.$implicit) {
      if (!this._thenViewRef) {
        // 从容器中分离视图而不破坏它。 与 insert() 一起使用可以复用视图。
        this._viewContainer.detach();
        if (this._thenTemplateRef) {
          this._thenViewRef =
            this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
        }
      } else  {
        this._viewContainer.detach()
        this._viewContainer.insert(this._thenViewRef)
      }
    } else {
      if (!this._elseViewRef) {
        this._viewContainer.detach()
        if (this._elseTemplateRef) {
          this._elseViewRef =
            this._viewContainer.createEmbeddedView(this._elseTemplateRef, this._context);
        }
      } else  {
        this._viewContainer.detach()
        this._viewContainer.insert(this._elseViewRef)
      }
    }
  }
  constructor(templateRef: TemplateRef<CacheContext<T>>,
              private readonly _viewContainer: ViewContainerRef) {
    this._thenTemplateRef = templateRef
  }

}

视图缓存逻辑

TemplateCacheDirective 使用 _thenViewRef_elseViewRef 两个变量来缓存已渲染的视图。当 appCache 的条件为 true 时,会检查 _thenViewRef 是否已存在,如果不存在,会创建视图并缓存它,以便以后复用。如果 _thenViewRef 已存在,它会被直接插入到 DOM 中。

类似地,当 appCache 的条件为 false 时,会检查 _elseViewRef 是否已存在,如果不存在,会创建视图并缓存它,以便以后复用。如果 _elseViewRef 已存在,它会被直接插入到 DOM 中。

性能考虑

值得注意的是,在动态模板的情况下,如何合理使用缓存是一个复杂的问题。动态模板可能包含多样性,且可能频繁变化,这可能导致缓存的效果并不明显。因此,在使用自定义缓存指令时,需要综合考虑模板的复杂性以及缓存所带来的性能提升。

使用自定义缓存指令

在前面的章节中,我们已经了解了如何创建一个自定义缓存指令 TemplateCacheDirective,它允许我们在 Angular 应用中根据条件进行动态渲染,并提供了缓存已渲染的视图的能力。现在,让我们一起看看如何在实际项目中使用这个自定义指令来优化我们的应用。

在模板中使用

假设我们有一个组件模板,希望根据不同的条件进行动态渲染,并在渲染过程中利用缓存提升性能。

<ng-container *appCache="condition; then thenTemplate; else elseTemplate"></ng-container>

<ng-template #thenTemplate>
  <!-- 这里是条件为 true 时的模板内容 我们这个使用view1组件测试 -->
  <app-view1></app-view1>
</ng-template>

<ng-template #elseTemplate>
  <!-- 这里是条件为 false 时的模板内容 我们这个使用view2组件测试 -->
  <app-view2></app-view2>
</ng-template>

  1. app-view1代码(app-view2代码一样的)

@Component({
  selector: 'app-test-view1',
  template: `
    <p>
      test-view1 works! {{count}}
    </p>
    <button (click)="add()">add</button>
  `,
  styles: [
  ]
})
export class TestView1Component implements DoCheck {
  count = 0

  add() {
    // 用于测试状态保持
    this.count ++;
  }
  constructor() {
    console.log('TestView1Component');
    // 结合ngDoCheck 测试 当从视图中分离后 是否还会继续执行变更检测
    setInterval(() => {}, 1000)
  }

  ngDoCheck() {
    console.log('ngDoCheck1');
  }
}
  1. 运行效果

  • 验证缓存是否生效 angular视图缓存-模板缓存 不管如何切换,view1组件和view2组件始终只实例化一次,说明缓存生效了

  • 验证状态保持

angular视图缓存-模板缓存 在view1中计数器 + 到5,然后切换到view2,再切换回view1,显示结果还是5,说明状态时保持的

  • 验证分离后的变更检测

angular视图缓存-模板缓存 从view1切换到view2后ngDoCheck1再也没输出,说明从视图中分离的同时也从变更检测树上分离

注意事项

虽然自定义缓存指令可以提升性能,但在使用时需要注意一些事项:

  • 动态模板:如果 thenTemplateelseTemplate 是动态生成的,可能会影响缓存的效果。这时,需要根据具体情况权衡是否适合使用缓存。
  • 缓存策略:在一些场景下,可能需要手动管理缓存的状态,根据实际需求决定何时缓存视图工厂函数,何时清除缓存(目前我还没实现)。
  • 性能测试:在应用性能优化中,性能测试是必不可少的。确保在使用自定义缓存指令后进行性能测试,以便评估它对应用性能的实际影响。

总结

本文深入探讨了自定义缓存指令的实现原理和用法。通过使用 TemplateCacheDirective,我们可以根据条件动态渲染模板,并在渲染过程中利用缓存提升性能。核心思想是通过缓存已渲染的视图工厂函数,避免了频繁的编译过程,从而提高了渲染效率。

在使用自定义缓存指令时,需要注意动态模板的情况,因为这可能影响缓存的效果。在动态模板的情况下,合理的缓存策略需要根据实际需求进行权衡。性能测试也是必不可少的,以确保自定义缓存指令在实际项目中确实带来了性能的提升。

通过在实际项目中的使用示例,我们验证了自定义缓存指令的效果。结合 app-view1app-view2 组件的测试,我们确认了缓存的有效性、状态保持功能以及视图分离后的变更检测行为。

您可以在 这里 找到本文中使用的自定义缓存指令的源代码。总之,自定义缓存指令是一个有益的工具,可以在某些情况下优化应用性能,提升用户体验。在实际项目中,根据具体需求和场景,选择合适的缓存策略,以获得最佳的性能优化效果。