Angular自定义下拉框动态数据触发循环导致页面卡顿问题求助
Angular 自定义多选下拉组件循环触发卡顿问题
问题场景
在Angular项目中开发了自定义多选下拉组件,父组件循环调用该组件时,下拉数据动态变化:
[Apple,orange,grapes,pineapple] row 2 dropdown [orange,grapes,pineapple] row 3 dropdown [orange,grapes,pineapple]
组件核心功能是创建包含下拉显示名称、选中状态、索引id的model数组,并向外emit选中的selectedData。但所有逻辑都在ngOnChanges钩子中实现,当父组件过滤数据传入时,会触发ngOnChanges调用initCheckModelStatus方法,进而触发emitSelectedData,导致下拉框change事件循环触发,页面出现卡顿。
核心问题
ngOnChanges每次触发都会无条件重置model和selected数组,强制重新初始化状态,无论输入属性是否真的需要更新- 初始化过程中无条件调用
emitSelectedData,导致父组件接收数据后可能重新触发数据过滤,形成循环 - 直接修改输入属性
this.data(this.data.splice(0, 0, { [this.itemName]: 'Select' })),会触发父组件的变更检测,加剧循环
解决方案
1. 优化ngOnChanges触发逻辑
只在关键输入属性(如data、selectedItems)发生实际变化时才执行初始化,避免不必要的重复执行:
- 通过
SimpleChanges的previousValue和currentValue判断属性是否真的变更 - 避免每次都重置所有状态,只在必要时更新
2. 避免修改输入属性
不要直接修改@Input()装饰的data数组,而是创建副本进行操作,防止触发父组件的变更检测:
// 替换直接修改this.data的代码 const dataCopy = [...this.data]; if (!this.statTemp) { dataCopy.splice(0, 0, { [this.itemName]: 'Select' }); // 后续操作使用dataCopy而非this.data }
3. 控制emitSelectedData的触发时机
仅在用户交互(inputClick)或选中状态真正变更时才向外emit数据,初始化阶段除非状态变化,否则不触发emit:
- 在
initCheckModelStatus前后对比选中状态,只有变化时才调用emitSelectedData - 移除初始化时无条件的emit逻辑
4. 优化变更检测策略
为组件设置ChangeDetectionStrategy.OnPush,减少不必要的变更检测触发:
@Component({ // ...其他配置项 changeDetection: ChangeDetectionStrategy.OnPush })
修改后的关键代码示例
优化后的ngOnChanges
ngOnChanges(changes: SimpleChanges) { // 仅在data或selectedItems实际变更时执行初始化 if (changes['data'] || changes['selectedItems']) { this.model = []; this.selected = []; this.showAdd = false; this.addNewLoad = false; this.textEntered = ''; // 保存初始化前的选中索引,用于后续对比 const prevSelectedIndexes = this.selected.map(item => this.data.indexOf(item)); this.initCheckModelStatus(); // 对比选中状态是否变化,仅变化时触发emit const currentSelectedIndexes = this.selected.map(item => this.data.indexOf(item)); if (JSON.stringify(prevSelectedIndexes) !== JSON.stringify(currentSelectedIndexes)) { this.emitSelectedData(); } } }
修改initCheckModelStatus中的数据操作
initCheckModelStatus(): void { if (this.buttonName) { this.value = this.buttonName; } else { this.value = this.itemName; } if (this.data) { const dataCopy = [...this.data]; // 创建数据副本,避免修改原输入属性 let statTemp = false; if (!this.multiselector && !this.toggleMultiSelect) { for (let i = 0; i < dataCopy.length; i++) { if (dataCopy[i][this.itemName] == 'Select') { statTemp = true; break; } } if (!statTemp) { dataCopy.splice(0, 0, { [this.itemName]: 'Select' }); if (this.selectedItems) { this.selectedItems = Object.assign([Number(this.selectedItems[0]) + 1]); } } } this.isDisabled = false; // 后续循环操作使用dataCopy而非原this.data for (let index = 0; index < dataCopy.length; index++) { let status = true; if (this.status) { if (dataCopy[index][this.status] && dataCopy[index][this.value]) { this.selected.push(dataCopy[index]); } this.itemNameCheck(index, dataCopy[index][this.status], dataCopy); } else if (this.selectedItems) { // ...原有选中状态判断逻辑,替换this.data为dataCopy } else { this.itemNameCheck(index, false, dataCopy); } } } else { this.isDisabled = true; } this.checkBoxValue = this.model.length === this.selected.length; // 移除原初始化时的emit逻辑 } // 同步修改itemNameCheck方法,接收dataCopy作为参数 itemNameCheck(index, status, dataSource) { if (dataSource[index][this.itemName]) { if(this.excludedList.length){ this.excludedList.forEach(item=>item[this.itemName] !== dataSource[index][this.itemName]) } if(this.showLogo){ this.model.push({ 'name': dataSource[index][this.itemName], 'id': index, 'status': status, 'flag_image_path': dataSource[index]?.flag_image_path }); } else { this.model.push({ 'name': dataSource[index][this.itemName], 'id': index, 'status': status, 'flag_image_path': null }); } } else if (dataSource[index]) { // ...其余逻辑,替换this.data为dataSource } }
内容的提问来源于stack exchange,提问作者Lovitha
相关产品推荐
相关产品推荐

