如何结合参数化Selector使用withLatestFrom?寻求高效实现方案
优化NgRx Effect中参数化Selector的使用方式
首先纠正你原selector里的一个小bug:item.id = myId是赋值操作,应该改为严格相等判断item.id === myId,否则会意外修改数据并导致逻辑错误:
mySelector = (myId: number) => createSelector( selectList, list => list.find(item => item.id === myId) );
针对你在Effect中使用参数化Selector的需求,以下是几种更优的实现方式:
方式一:使用withLatestFrom的函数式写法(最简洁)
你最初尝试的withLatestFrom写法无效,是因为没有利用它支持动态生成Observable的特性。withLatestFrom可以接收一个函数,该函数会接收当前action作为参数,这样就能正确获取itemId并传入selector:
myEffect$ = createEffect(() => { return this.actions$.pipe( ofType(myAction), // 这里用函数式参数,直接通过action拿到itemId withLatestFrom((action) => this.store.select(mySelector(action.itemId))), map(([{ itemId }, myItem]) => { // 后续操作,同时拥有itemId和myItem }) ); });
这种写法最简洁,适合action触发时,目标数据已经存在于store中的场景,直接获取当前最新值即可。
方式二:switchMap + take(1)(更安全,避免内存泄漏)
如果需要确保只获取一次数据(不需要持续监听store变化),或者需要处理“action触发后store才更新数据”的场景,推荐用switchMap替代mergeMap,并加上take(1)终止订阅:
myEffect$ = createEffect(() => { return this.actions$.pipe( ofType(myAction), // switchMap会取消前一次未完成的订阅,避免并行处理导致的混乱 switchMap(({ itemId }) => this.store.select(mySelector(itemId)).pipe( take(1), // 只取一次当前值,立即结束订阅,防止内存泄漏 map(myItem => ({ myItem, itemId })) ) ), map(({ myItem, itemId }) => { // 后续操作 }) ); });
switchMap对比mergeMap:如果短时间内多次触发myAction,mergeMap会同时维持多个store订阅,而switchMap会取消前一个订阅,只处理最新的action,更符合多数业务场景。take(1):如果不需要监听store中该数据的后续变化,一定要加这个操作符,避免不必要的订阅占用资源。
方式三:concatMap(按顺序处理action)
如果业务要求必须按action触发的顺序依次处理(不允许并行),可以用concatMap替代switchMap,它会将请求排队,前一个处理完成后再处理下一个:
myEffect$ = createEffect(() => { return this.actions$.pipe( ofType(myAction), concatMap(({ itemId }) => this.store.select(mySelector(itemId)).pipe( take(1), map(myItem => ({ myItem, itemId })) ) ), map(({ myItem, itemId }) => { // 后续操作 }) ); });
对原写法的优化建议
你当前的mergeMap写法如果不加take(1),会持续订阅store中对应id的数据变化,若不需要监听后续更新,建议加上take(1)避免内存泄漏。
内容的提问来源于stack exchange,提问作者Natalia Cáceres
相关产品推荐
相关产品推荐

