Edge浏览器中Angular 5的*ngFor绑定复选框点击响应缓慢问题
看起来你在Angular 5项目里遇到了Edge浏览器下大列表单选操作的性能瓶颈,我来帮你拆解问题并给出具体的优化方案:
1. 先修复数据引用的潜在bug
你在setData方法里复制数据的方式存在逻辑问题:
let d = data[0]; d.isSelected = false; data.push(d);
这里的d是对data[0]的引用,所以你push的2000条数据其实都是同一个对象实例。也就是说,当你修改任意一条的isSelected,所有项都会同步变化——这不仅不符合业务逻辑,还会让Angular做很多无用的变更检测工作。
改成浅拷贝对象来修复:
for (let i = 0; i < 2000; i++) { // 浅拷贝原对象,避免引用同一实例 let d = {...data[0]}; d.isSelected = false; data.push(d); }
2. 给*ngFor添加trackBy函数(核心性能优化)
当前的*ngFor没有设置trackBy,这会导致每次数组内对象的属性变化时,Angular会销毁并重新渲染所有2500个列表项。Edge浏览器的DOM操作效率本身就比Chrome/Firefox低,这种全量重渲染会直接导致卡顿。
步骤:
- 在
HomePage组件里新增trackBy函数:
trackByIndex(index: number, item: any): number { return index; // 如果数据有唯一ID(比如item.id),用ID会更稳妥 }
- 在模板里修改
*ngFor:
<ul *ngFor="let item of commentArray; let i = index; trackBy: trackByIndex;">
这样Angular只会重新渲染实际发生变化的列表项,而不是全量更新。
3. 简化单选逻辑,避免全量循环
你当前的changeCheckbox函数会循环2500条数据,逐个修改isSelected——这会触发2500次对象属性变更,每次变更都会触发Angular的变更检测,性能消耗极大。
优化方案(二选一):
方案A:只修改必要的项
新增一个变量记录当前选中的索引,只修改之前选中项和当前项的状态:
// 新增变量保存当前选中的索引 selectedIndex: number = -1; changeCheckbox(index){ console.time("Performance"); // 仅修改之前选中的项和当前项 if(this.selectedIndex !== -1){ this.commentArray[this.selectedIndex].isSelected = false; } this.commentArray[index].isSelected = true; this.selectedIndex = index; console.timeEnd("Performance"); }
这样循环次数从2500次降到最多2次,性能提升非常明显。
方案B:利用radio原生单选特性(更推荐)
既然你要的是单选效果,完全可以借助radio的原生行为,不用手动处理取消其他选项:
修改模板代码:
<li> <span>{{i}}</span> <input type="radio" name="commentRadioGroup" [(ngModel)]="selectedIndex" [value]="i"> <span>{{item.name}}</span> </li>
然后删除changeCheckbox函数,同时可以去掉isSelected属性(如果业务不需要单独存储的话)。这种方式由Angular双向绑定处理单选逻辑,代码更简洁,性能也更好。
4. 启用OnPush变更检测策略
对于列表类组件,设置OnPush变更检测策略可以让Angular只在输入属性变化或组件内触发事件时才进行变更检测,减少不必要的检测次数。
修改组件装饰器:
import { Component, ChangeDetectionStrategy } from '@angular/core'; @Component({ selector: 'page-home', templateUrl: 'home.html', changeDetection: ChangeDetectionStrategy.OnPush // 添加这一行 }) export class HomePage { // ... 其他代码 }
5. 批量更新时减少变更检测触发
如果需要做批量数据修改(比如初始化时设置isSelected),可以把修改逻辑放在Angular的变更检测上下文之外,完成后再手动触发一次检测:
import { NgZone, ChangeDetectorRef } from '@angular/core'; // 注入NgZone和ChangeDetectorRef constructor( public navCtrl: NavController, private http: Http, private ngZone: NgZone, private cdr: ChangeDetectorRef ) {} setData(data) { this.ngZone.runOutsideAngular(() => { // 在变更检测外批量修改数据 for (let i = 0; i < data.length; i++) { data[i]['isSelected'] = false; } for (let i = 0; i < 2000; i++) { let d = {...data[0]}; d.isSelected = false; data.push(d); } }); this.commentArray = data; this.cdr.detectChanges(); // 手动触发一次变更检测 }
内容的提问来源于stack exchange,提问作者venishk

