Redux Toolkit `useAppSelector` 值在未更新时导致渲染

Redux Toolkit `useAppSelector` value causes render when it isn't updated

背景

标题可能令人困惑,但为了解释,我在 Redux Toolkit 中使用切片来管理状态。这些切片包含我的地图应用程序的各种状态值。具体来说,我遇到的两个渲染问题是单击位置(称为 focusedPosition 和鼠标坐标 mouseCoords)。这些切片中的每一个都有一个 latlng 值。使用 react-leaflet,每当用户单击地图以显示弹出窗口时,我都会更新 focusedPosition 的地图事件。我还有一个事件来捕获 mouseCoords 以显示在地图的一角。但是,出于某种原因,任何订阅 focusedPosition 的组件都会在鼠标移动时更新 re-render——即使它没有订阅 mouseCoords。这会导致多个问题,包括性能问题以及地图弹出窗口闪烁,因为它们会在鼠标移动时不断 re-rendering。 如果我注释掉 react-leaflet 中的 mousemove 事件,问题就会停止,因为该值不再更新,但这不是一个选项,因为我确实需要捕获这些鼠标坐标。

我如何确定为什么这两个值以某种方式链接在一起,我该如何解决这个问题?

适用代码如下,还有一个Code Sandbox

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

您的 getFocusedPosition 选择器在每次调用 reducer 时创建一个新对象。

由于当 oldSelectorResult !=== newSelectorResult 并且这些对象在引用上不相等时 react-redux 重新渲染,这将导致重新渲染。

您可以 select 完整切片状态(如果将来添加更多道具,可能会出现过度使用的风险)

export const getFocusedPosition = (state: RootState) => state.focusedPosition;

或者创建一个记忆化的select或者当输入值改变时仅returns一个新对象(参见https://redux.js.org/recipes/computing-derived-data):

export const getFocusedPosition = createSelector(
  state => state.focusedPosition.lat,
  state => state.focusedPosition.lng,
  (lat, lng) => ({ lat, lng })
)

或者您只是分别订阅这两个值:

const lat = useAppSelector(state => state.focusedPosition.lat)
const lng = useAppSelector(state => state.focusedPosition.lng)

所有这些都在 useSelector 文档中进一步讨论:https://react-redux.js.org/api/hooks#equality-comparisons-and-updates