Angular变更检测机制

news2024/10/7 20:35:01

前段时间遇到这样一个 bug,通过一个 click 事件跳转到一个新页面,新页面迟迟不加载;
经过多次测试发现,将鼠标移入某个 tab ,页面就加载出来了。

举个例子,页面内容无法加载,但是将鼠标移入下图的 消息 或者 历史 tab,页面就加载出来了。

在这里插入图片描述

正常情况下,Angular 会在 组件初始化完毕后 自动进行变更检测,并更新视图。

但有时候,你可能希望在 组件实例化后 立即执行一次变更检测,以确保视图能够及时更新。这种情况下,我们可以在构造函数之后的代码中显式调用 detectChanges() 方法。

为了在切换路由后立即进行 变更检测 并渲染新页面,在路由导航结束后,Angular 提供了一个 Router.events 事件流来监听路由导航的状态。开发者可以订阅 NavigationEnd 事件,并在回调函数中调用 detectChanges() 方法。

import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';

@Component({
	// 组件元数据
})
export class MyComponent implements OnInit {
  constructor(
  	private router: Router,
  	private cdr: ChangeDetectorRef,
  ) {}

  ngOnInit() {
    this.router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        this.cdr.detectChanges(); // 在路由切换完成后调用 detectChanges
      }
    });
  }
  
}

如此,就实现了跳转页面视图不展示的问题。

这里,简单介绍一下 Angular 的变更检测策略,包括 默认策略OnPush 策略无策略

要关闭变更检测,可以将变更检测策略设置为 无策略ChangeDetectionStrategy.OnPush)或 手动调用 detach() 方法来分离变更检测器。

1 变更检测策略

1.1 手动分离变更检测器

使用 detach() 方法来分离变更检测器,这样组件就不再与变更检测关联。

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

@Component({
  selector: 'app-my-component',
  templateUrl: './my-component.component.html'
})
export class MyComponent implements OnInit {
  constructor(private cdr: ChangeDetectorRef) {}

  ngOnInit() {
    this.cdr.detach(); // 分离变更检测器
  }
}

在此方法中,我们需要将 ChangeDetectorRef 注入到组件中,并在 ngOnInit 生命周期钩子函数中调用 detach() 方法。这样就会分离变更检测器,从而关闭变更检测。

1.2 使用变更检测策略

将组件的变更检测策略设置为 ChangeDetectionStrategy.OnPush,这会使得组件仅在输入属性发生变化时才进行变更检测。我们可以在组件的元数据中指定变更检测策略。

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

@Component({
  selector: 'app-my-component',
  templateUrl: './my-component.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush // 设置变更检测策略为 OnPush
})
export class MyComponent {}

使用此策略时,我们需要手动触发变更检测,例如通过注入 ChangeDetectorRef 并调用 detectChanges() 方法。

需要注意的是,关闭变更检测可能导致视图无法及时更新,因此应仔细考虑是否真正需要关闭变更检测。一般情况下,使用默认的变更检测策略并让 Angular 自动执行变更检测是推荐的做法。只有在特定的性能优化需求下才应该考虑手动关闭变更检测。

2 ChangeDetectionStrategy.OnPush 策略

这里,我们要注意,将组件的变更检测策略设置为 ChangeDetectionStrategy.OnPush,这会使得组件仅在 输入属性 发生变化时才进行变更检测。输入属性指的是通过 @Input 装饰器定义在组件上的属性。这些属性用于从父组件向子组件传递数据。

当将组件的变更检测策略设置为 ChangeDetectionStrategy.OnPush 时,组件只会在 输入属性 发生变化时才触发变更检测和重新渲染。

举个例子,MyComponent 的组件,定义了一个输入属性 data,只有每次 data 的值发生变化, Angular 才会自动触发变更检测并更新组件的视图。

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

@Component({
  selector: 'app-my-component',
  template: `
    <div>{{ data }}</div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush // 设置变更检测策略为 OnPush
})
export class MyComponent {
  @Input() data: string;
}

通过将组件的变更检测策略设置为 ChangeDetectionStrategy.OnPush,我们可以确保只有父组件修改了子组件的输入属性或者父组件手动调用 markForCheck() 方法,子组件才会执行变更检测、重新渲染,如此可以提高性能并减少不必要的变更检测。

事实上,除了输入属性发生变化,组件自身的事件(例如按钮点击、定时器等),也可以触发变更检测;

注意,当只有子组件内部的变量值发生改变而没有其他触发条件满足时,ChangeDetectionStrategy.OnPush 策略下的组件不会自动触发变更检测。这意味着组件的视图不会被更新。

2.1 子组件 ngModule 值变化

这里,可能会有人提问,子组件的 ngModule 值变化,会不会触发子组件的变更检测和重新渲染。

答案是不会,ngModuleAngular 中用于定义模块的装饰器函数,它通常在应用程序的根模块中使用,并且在启动时就确定了,它的变化不会被视为触发变更检测的条件。

子组件input框的 ngModule 值发生变化时,并不能直接触发子组件的变更检测,那么 input 框中内容会发生变化吗?
input 框的 ngModule 值发生变化时,输入框中的内容仍然会根据新的值进行更新,因为这是由 ngModel 实现的。这种更新是通过 DOM 事件(如 inputchange)来触发的,而不是通过 Angular 的变更检测系统。

2.2 子组件 内部数据发生变化

这里,举一个内部数据发生变化的例子。

  • 使用 ChangeDetectionStrategy.OnPush 策略;

count 的值定时发生变化,但是页面并未更新;

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

@Component({
  selector: 'test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.less'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})

export class TestComponent implements OnInit, OnDestroy {
  constructor(
    private router: Router,
    private cdr: ChangeDetectorRef,
  ) { }

  count = 1;
  ngOnInit(): void {
    setInterval(() => {
      this.count++;
    }, 1000);
  }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 不使用 ChangeDetectionStrategy.OnPush 策略;

页面数据会刷新

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

2.3 父组件传值发生变化

父组件传值,直接修改了可变对象(如:数组、对象等),而没有创建一个新的引用,那么组件可能无法检测到这个改变,从而不会触发变更检测。

items: string[] = ['Item 1', 'Item 2', 'Item 3'];
this.items.push('New Item');

3 关闭变更检测

Angular 中,我们可以通过使用 ChangeDetectorRef 来关闭或禁用变更检测。ChangeDetectorRef 是一个服务,提供了与变更检测相关的方法。以下是一种关闭变更检测的方法:

注入 ChangeDetectorRef 服务:

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

@Component({
  selector: 'app-example',
  template: `
    <!-- Your component's template -->
  `
})
export class ExampleComponent {
  constructor(private cdr: ChangeDetectorRef) {}
}

在需要关闭变更检测的地方调用 detach() 方法

this.cdr.detach();

调用 detach() 方法会将组件从变更检测树中分离,意味着该组件及其子组件将不再进行自动的变更检测和视图更新。这可以帮助减少不必要的计算和渲染,提高性能。

需要注意的是,一旦你将组件从变更检测树中分离,它将不再自动响应输入属性的变化或事件的触发。如果你需要手动控制变更检测,可以使用 markForCheck() 方法来通知 Angular 运行变更检测。例如:

this.cdr.markForCheck();

使用 markForCheck() 方法可以标记组件及其父组件,使其在下一次变更检测周期中进行变更检测。

总结起来,通过 ChangeDetectorRefdetach() 方法,我们可以关闭变更检测以提高性能,并使用 markForCheck() 方法手动触发变更检测。请注意,在大多数情况下,不需要手动关闭变更检测,Angular 的默认行为通常已经足够高效和准确。

this.cdr.markForCheck()this.cdr.detectChanges()是Angular中的变更检测相关方法,它们有一些区别和适用场景。

  • this.cdr.markForCheck(): 调用markForCheck()方法会通知 Angular 在下一次变更检测周期中检查组件及其子组件,并执行相应的变更检测操作。该方法将标记组件为“已脏”(dirty),意味着组件可能发生了变化,需要进行检查。这对于在组件中进行异步操作,但可能没有直接触发变更的情况非常有用。调用markForCheck()方法本身不会立即触发变更检测,而是等待下一次变更检测周期执行。

  • this.cdr.detectChanges(): 调用detectChanges()方法会立即触发一次变更检测,无论组件是否已被标记为“脏”。这将从组件树的根节点开始进行变更检测,并检查任何已标记为“脏”的组件。这可以用来强制立即进行变更检测,而不必等待下一次自动变更检测周期。但是,过度使用detectChanges()方法可能会导致性能问题,因为它执行了全面的变更检测,而不只是对标记为“脏”的组件进行检查。

从使用角度来看,通常情况下应该优先使用this.cdr.markForCheck()方法,因为它更高效且更符合 Angular 的变更检测机制。只有在特殊情况下,如在组件中调用了异步操作但未触发变更时,才应该使用this.cdr.detectChanges()方法来强制立即进行变更检测。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1015011.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NO.396 旋转数组

题目 给定一个长度为 n 的整数数组 nums 。 假设 arrk 是数组 nums 顺时针旋转 k 个位置后的数组&#xff0c;我们定义 nums 的 旋转函数 F 为&#xff1a; F(k) 0 * arrk[0] 1 * arrk[1] ... (n - 1) * arrk[n - 1] 返回 F(0), F(1), ..., F(n-1)中的最大值 。 生成…

深度学习-卷积神经网络-纹理表示卷积神经网络-卷积神经网络-[北邮鲁鹏]

这里写目录标题 参考文章全连接神经网络全连接神经网络的瓶颈全连接神经网络应用场景 卷积神经网络卷积层(CONV)卷积核卷积操作卷积层设计卷积步长(stride)边界填充特征响应图组尺寸计算 激活层池化层(POOL)池化操作定义池化操作作用池化层超参数常见池化操作 全连接层(FC)样本…

linux相关知识以及有关指令3

在linux的世界中我们首先要有万物皆文件的概念&#xff0c;那么在系统中有那么多的文件&#xff0c;我们该怎么区分呢&#xff1f;文章目录 1. 文件分类2. 文件的权限1). 拥有者和所属组以及other2). 文件的权限3). 粘滞位4). 对于权限修改的拓展知识点a.修改权限b.修改拥有者所…

智慧工地平台源码 劳务实名制、视频监控、扬尘监测、起重机械安全监测

伴随着技术的不断发展&#xff0c;信息化手段、移动技术、智能穿戴及工具在工程施工阶段的应用不断提升&#xff0c;智慧工地概念应运而生&#xff0c;智慧工地平台围绕施工现场管理&#xff0c;构建全方位的智能监控防范体系弥补传统方法和技术在监管中的缺陷&#xff0c;形成…

分享一个Python 写的监控日志log txt文档 的代码

监控log文件的需求 某些特殊原因&#xff0c;想一直看到.log 的最后一行打印&#xff0c;所以写了一些代码监控log &#xff08;有个奇怪需求&#xff0c;就是log 因为重复启动原因&#xff0c;会一直加&#xff0c;不是同一个log&#xff09; 监控界面 涉及的Python代码&…

管理类联考——数学——汇总篇——知识点突破——代数——数列——秒杀

&#x1f41f; ⛲️ 特殊值秒解数列 当数列题目中只有一个条件时&#xff0c;在不违背题意的条件下&#xff0c;可以直接利用特殊值&#xff0c; 令其公差为0或公比为1。 注意&#xff1a;一定要检验是否符合题意&#xff0c;题目中如果出现公差不为0或公比不为1&#xff0c;则…

华为云云耀云服务器L实例评测|部署前后端分离项目

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 学习测评 ✨特色专栏&#xff1a; MyS…

[NLP] LLM---<训练中文LLama2(一)>训练一个中文LLama2的步骤

一 数据集 【Awesome-Chinese-LLM中文数据集】 【awesome-instruction-dataset】【awesome-instruction-datasets】【LLaMA-Efficient-Tuning-数据集】Wiki中文百科&#xff08;25w词条&#xff09;wikipedia-cn-20230720-filteredBaiduBaiKe&#xff08;563w词条&#xff09; …

VIRTIO-SCSI代码分析(3)VIRTIO SCSI数据流处理

VIRTIO SCSI整体数据流如下&#xff1a; IO请求下发过程 虚拟机中通过FIO等下发IO请求&#xff0c;IO请求通过VFS/filesystem&#xff0c;BLOCK层&#xff0c;然后到SCSI层&#xff0c;传递给virtio-scsi驱动&#xff0c;virtio-scsi驱动通过virtioscsi_commit_rqs()下发IO请求…

利用 SOAR 加快事件响应并加强网络安全

随着攻击面的扩大和攻击变得越来越复杂&#xff0c;与网络攻击者的斗争重担落在了安全运营中心 &#xff08;SOC&#xff09; 身上。SOC 可以通过利用安全编排、自动化和响应 &#xff08;SOAR&#xff09; 平台来加强组织的安全态势。这一系列兼容的以安全为中心的软件可加快事…

【100天精通Python】Day63:Python可视化_Matplotlib绘制子图,子图网格布局属性设置等示例+代码

目录 1 基本子图绘制示例 2 子图网格布局 3 调整子图的尺寸 4 多行多列的子图布局 5 子图之间的共享轴 6 绘制多个子图类型 7 实战&#xff1a; 绘制一个大图&#xff0c;里面包含6个不同类别的子图&#xff0c;不均匀布局。 绘制子图&#xff08;subplots&#xff09;…

redis的安装、基础命令学习、常用数据结构

文章目录 前言一、Redis安装1.Ubuntu下安装&#xff08;1&#xff09;切换到root用户下&#xff08;2&#xff09;使用apt安装redis5&#xff08;3&#xff09;为了使redis支持远程连接&#xff0c;修改以下地方&#xff08;4&#xff09;验证安装是否成功 2.Centos7下安装&…

基于Java+SpringBoot+Vue+uniapp点餐小程序(包含协同过滤算法和会员系统,强烈推荐!)

校园点餐小程序 一、前言二、我的优势2.1 自己的网站2.2 自己的小程序&#xff08;小蔡coding&#xff09;2.3 有保障的售后2.4 福利 三、开发环境与技术3.1 MySQL数据库3.2 Vue前端技术3.3 Spring Boot框架3.4 微信小程序 四、功能设计4.1 系统功能结构设计4.2 主要功能描述 五…

理清Spring事务的核心关键类

在spring事务源码的内部&#xff0c;会有几个比较核心关键的类&#xff0c;虽然这基本上都是源码内部才使用到的类&#xff0c;但是有时候我们要对其做一些改造的时候免不了要去使用到这些类&#xff0c;并且这些类在spring事务内部都是起到关键的作用&#xff0c;对了解spring…

[2023-09-13]使用EXPDP/IMPDP迁移数据库后统计信息引起的性能问题

问题描述&#xff1a; 客户在使用expdp/impdp迁移数据库完成后&#xff0c;在新环境收集统计信息&#xff0c;但是在迁移完成的当天中午&#xff0c;好多SQL语句执行变慢&#xff0c;执行计划发生了改变&#xff0c;下面通过案例来说明。 1、准备数据 scott用户下创建test表&…

进程控制再学习

0.“开两个终端窗口” 因为学校用的虚拟机&#xff0c;得用终端登录&#xff0c;不能像shell一样直接复制窗口。 我们只需要登录两次就可以了&#xff08;方便监视&#xff09; 1.ps 1.循环ps while :;do ps -f;ps -ef|grep zombie;done 每秒打印一次&#xff1a; while :…

基于SSM+Vue的校园教务系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

报修工单系统如何提升维修维保工作效率?

在日常的报修维修工作中&#xff0c;我们经常会遇到一些挑战。其中数据分析是一项艰巨的任务&#xff0c;因为我们需要处理大量的数据并从中提取有价值的信息。维修工单的统计也是一个复杂的过程&#xff0c;需要对工单进行分类和整理&#xff0c;以便更好地了解工作的进度和效…

sudo+vim+g++/gcc+makefile+进度条

目录 一、信任表中加入指定的普通用户&#xff08;使其能使用sudo&#xff09; 二、vim的使用 &#xff08;一&#xff09;基本概念 1. 正常/普通/命令模式(Normal mode) 2. 插入模式(Insert mode) 3. 末行模式(last line mode) &#xff08;二&#xff09;vim正常模式…

国庆中秋特辑(一)浪漫祝福方式 用循环神经网络(RNN)或长短时记忆网络(LSTM)生成祝福诗词

目录 一、使用深度学习中的循环神经网络&#xff08;RNN&#xff09;或长短时记忆网络&#xff08;LSTM&#xff09;生成诗词二、优化&#xff1a;使用双向 LSTM 或 GRU 单元来更好地捕捉上下文信息三、优化&#xff1a;使用生成对抗网络&#xff08;GAN&#xff09;或其他技术…