触发ngRx的SetSensors Action后应用崩溃,无报错如何排查?
问题描述
我编写了如下ngRx reducer:
export type Action = SensorsActions.SensorsActions const defaultState: ISensor[] = [] export function sensorsReducer(state: ISensor[] = defaultState, action: any){ switch (action.type){ case SensorsActions.ADD_SENSOR: return [...state, action.payload] case SensorsActions.DELETE_SENSOR: return state.filter(s => s.id !== action.payload.id) case SensorsActions.UPDATE_SENSOR: let number = state.findIndex(s => s.id == action.payload.id) const sensors = [...state] sensors[number] = action.payload return sensors case SensorsActions.SET_SENSORS: return action.payload default: return state } }
组件中定义了Observable状态:
sensors$: Observable<ISensor[]> constructor( private sensorsService: SensorsService, private store: Store<SensorsTableState> ) { this.sensors$ = this.store.select('sensors') }
UI中展示传感器对象的表格,并有一个用于过滤对象的按钮:
<button class="search-button" (click)="filterSensors()">
filterSensors(): void { if (this.searchQuery !== undefined){ this.sensors$.subscribe(sensors => { const result = filterSensors(sensors, this.searchQuery) this.store.dispatch(new SensorsActions.SetSensors(result)) }) this.setPages() } } export const filterSensors = (sensors: ISensor[], query: string): ISensor[] => { return sensors.filter(sensor => { return Object.values(sensor).find(value => { const isValidValue = value && (typeof value === "string" || typeof value === "number") return isValidValue && value.toString().toLowerCase().includes(query.toLowerCase()) }) }) }
点击该按钮后应用崩溃,甚至无法刷新页面,但控制台与终端均无错误提示。过滤方法可正确返回所需对象,且在ngOnInit方法中通过相同的dispatch操作设置从服务器获取的传感器时运行正常,请问问题出在哪里?
问题原因与解决方案
核心问题:无限循环导致内存耗尽
你在filterSensors方法里的逻辑触发了无限循环:
- 订阅
store.select('sensors')返回的sensors$后,在回调中dispatchSET_SENSORS更新store的sensors状态 - store状态更新会让
sensors$再次发射新值,触发订阅回调重新执行,再次dispatchSET_SENSORS - 循环往复短时间内产生大量状态更新,直接耗尽浏览器内存,导致应用崩溃,控制台来不及打印错误。
ngOnInit中的dispatch没问题,是因为它只执行一次,不会触发后续的订阅回调循环。
解决方案
方案1:用RxJS管道处理过滤(推荐)
过滤属于视图层展示逻辑,无需修改store原始数据。通过combineLatest组合原始数据和查询条件,自动更新过滤结果:
import { combineLatest, BehaviorSubject } from 'rxjs'; import { map } from 'rxjs/operators'; // 组件内新增 filteredSensors$: Observable<ISensor[]>; private searchQuerySubject = new BehaviorSubject<string>(''); constructor(private store: Store<SensorsTableState>) { this.sensors$ = this.store.select('sensors'); // 组合数据流,自动计算过滤结果 this.filteredSensors$ = combineLatest([this.sensors$, this.searchQuerySubject]).pipe( map(([sensors, query]) => query ? filterSensors(sensors, query) : sensors) ); } filterSensors(): void { // 仅更新查询条件,无需订阅和dispatch this.searchQuerySubject.next(this.searchQuery); this.setPages(); }
之后UI直接绑定filteredSensors$即可,完全不改动store原始数据。
方案2:封装NgRx Selector(适合多组件复用)
把过滤逻辑封装成NgRx selector,由selector负责计算过滤结果:
// selector文件中 import { createSelector } from '@ngrx/store'; export const selectSensors = (state: SensorsTableState) => state.sensors; export const selectFilteredSensors = createSelector( selectSensors, (state, props) => props.query, (sensors, query) => query ? filterSensors(sensors, query) : sensors ); // 组件内使用 filterSensors(): void { if (this.searchQuery) { this.filteredSensors$ = this.store.select(selectFilteredSensors, { query: this.searchQuery }); } else { this.filteredSensors$ = this.store.select(selectSensors); } this.setPages(); }
方案3:限制订阅执行次数(不推荐,会覆盖原始数据)
如果一定要通过修改store状态实现过滤,需用take(1)确保订阅仅执行一次,打破循环:
import { take } from 'rxjs/operators'; filterSensors(): void { if (this.searchQuery !== undefined){ this.sensors$.pipe(take(1)).subscribe(sensors => { const result = filterSensors(sensors, this.searchQuery) this.store.dispatch(new SensorsActions.SetSensors(result)) }) this.setPages() } }
注意:此方式会覆盖store中的原始传感器数据,再次过滤只能基于上次结果,无法恢复原始数据,除非重新从服务器拉取。
内容的提问来源于stack exchange,提问作者Matisiuk
相关产品推荐
相关产品推荐

