Redux Toolkit `useAppSelector` 值在未更新时导致渲染
Redux Toolkit `useAppSelector` value causes render when it isn't updated
背景
标题可能令人困惑,但为了解释,我在 Redux Toolkit 中使用切片来管理状态。这些切片包含我的地图应用程序的各种状态值。具体来说,我遇到的两个渲染问题是单击位置(称为 focusedPosition
和鼠标坐标 mouseCoords
)。这些切片中的每一个都有一个 lat
和 lng
值。使用 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
背景
标题可能令人困惑,但为了解释,我在 Redux Toolkit 中使用切片来管理状态。这些切片包含我的地图应用程序的各种状态值。具体来说,我遇到的两个渲染问题是单击位置(称为 focusedPosition
和鼠标坐标 mouseCoords
)。这些切片中的每一个都有一个 lat
和 lng
值。使用 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