Redux Toolkit中useAppSelector监听值未更新却触发组件渲染如何解决?
问题背景
我在地图应用中使用Redux Toolkit的slice管理状态,这些slice存储了应用的各类状态值。目前遇到重渲染问题的两个状态分别是点击定位坐标focusedPosition和鼠标实时坐标mouseCoords,二者均包含lat、lng两个字段。我通过react-leaflet的地图事件,实现用户点击地图时更新focusedPosition以展示弹窗,同时监听鼠标移动事件更新mouseCoords以在地图角落展示当前坐标值。但实际运行时发现,所有订阅了focusedPosition更新的组件会在鼠标移动时触发重渲染,即便这些组件并未订阅mouseCoords,这不仅引发性能问题,还会导致地图弹窗因频繁重渲染出现闪烁。
如果注释掉react-leaflet中的mousemove事件,该问题就会消失,因为mouseCoords不再更新,但我必须保留鼠标坐标采集能力,因此该方案不可行。
请问我该如何排查这两个状态被关联触发更新的原因,以及如何修复该问题?
相关代码
store.ts
export const store = configureStore({ reducer: { focusedPosition: focusedPositionReducer, mouseCoords: mouseCoordsReducer, } }) // Export AppDispatch // Export RootState // Export AppThunk
focusedPositionSlice.tsx
interface FocusedPositionState { lat: number | null lng: number | null } const initialState: FocusedPositionState = { lat: null, lng: null, } export const focusedPositionSlice = createSlice({ name: 'focusedPosition', initialState, reducers: { clearFocusedPosition: state => { state.lat = null state.lng = null }, setFocusedPosition: (state, action: PayloadAction<FocusedPositionState>) => { state.lat = action.payload.lat state.lng = action.payload.lng } } }) // Export actions // Export getFocusedPosition selector // Export default reducer
mouseCoordsSlice.tsx
interface MouseCoordsState { lat: number lng: number } const initialState: MouseCoordsState = { lat: 0, lng: 0, } export const mouseCoordsSlice = createSlice({ name: 'mouseCoords', initialState, reducers: { setMouseCoords: (state, action: PayloadAction<MouseCoordsState>) => { state.lat = action.payload.lat state.lng = action.payload.lng } } }) // Export actions // Export getMouseCoords selector // Export default reducer
排查思路
- 优先检查
getFocusedPositionselector的实现:Redux的useSelector默认使用严格引用比较判断是否需要触发重渲染,如果你在selector中直接返回了新构造的对象(比如每次都手动解构lat、lng后返回新对象{lat: xxx, lng: xxx}),那么只要Redux全局状态有任何更新(包括mouseCoords更新),selector都会重新执行并返回新引用的对象,useSelector会认为状态发生了变化,进而触发组件重渲染。 - 其次检查组件是否错误订阅了全局状态:确认组件没有直接引入整个RootState,仅通过selector订阅需要的
focusedPosition相关状态。
修复方案
根据排查出的selector问题,可选择以下任意一种方案修复:
- 直接返回slice原引用
修改getFocusedPositionselector,直接返回slice的根状态对象,RTK使用Immer更新状态时,未修改的slice引用会保持不变,只有对应action触发时才会更新引用,避免无效重渲染:
export const getFocusedPosition = (state: RootState) => state.focusedPosition
- 使用createSelector缓存计算结果
如果确实需要对返回值做格式化处理,使用RTK自带的createSelector创建 memoized selector,只有当依赖的focusedPosition状态实际变化时才会重新计算返回新对象:
import { createSelector } from '@reduxjs/toolkit' const selectFocusedPositionRoot = (state: RootState) => state.focusedPosition export const getFocusedPosition = createSelector( [selectFocusedPositionRoot], (pos) => ({ lat: pos.lat, lng: pos.lng }) )
- 给useSelector添加浅比较规则
如果不想修改selector,也可以在调用useSelector时传入第二个比较参数,使用浅比较判断值是否变化:
import { shallowEqual, useSelector } from 'react-redux' // 组件内使用 const focusedPosition = useSelector(getFocusedPosition, shallowEqual)
额外优化建议
针对mouseCoords这种更新频率极高的状态,如果仅在地图组件及子组件内使用,不需要跨全局共享,建议直接存在组件局部状态中,避免频繁更新全局状态触发所有selector重新计算,进一步提升性能。
内容的提问来源于stack exchange,提问作者c-mcb92
相关产品推荐
相关产品推荐

