使用Redux与createSelector时React组件不更新,UI变更未检测
Immutable Redux状态下选择器导致组件不更新的问题
我在Immutable Redux状态中创建了一个群组内用户的选择器:
import { useStore } from 'react-redux'; import { createSelector } from '@reduxjs/toolkit' const selectGroup = (state) => state.group; const memoizedUsers = createSelector( [selectGroup], (group) => { return group.get('users').toJS(); } ) const useMemoizedUsers = () => { const store = useStore(); return memoizedUsers(store.getState()); }
随后在组件中展示用户列表并提供删除用户的选项:
const DeleteButton = ({ rowIndex }) => { const dispatch = useDispatch(); return ( <Button onClick={() => { dispatch({ type: '@@group/removeUser', payload: { rowIndex } }) }} > Delete </Button> ) } const columnDefs = [ { headerName: 'Name', field: 'name', }, { headerName: 'Age', field: 'age', }, { headerName: '', field: 'onDelete', cellRenderer: ({ node: { rowIndex } }) => { return ( <DeleteButton rowIndex={rowIndex} /> ) } } ]; const UserList = () => { const users = useMemoizedUsers(); return ( <Grid rowData={users} columnDefs={columnDefs} /> ) }
点击删除按钮后,Redux store已更新(用户从10个变为9个),但UserList组件未重新渲染,UI仍显示全部10个用户。
我想知道是createSelector使用错误,还是如何强制组件更新?
若修改获取users的方式为:
const users = useSelector((state) => state.group.get('users')?.toJS() )
组件会重新渲染且列表更新,但会收到Redux警告:相同参数调用时选择器返回不同结果,可能导致不必要重渲染。
另外,改写UserList组件为:
const UserList = () => { const users = useSelector((state) => state.group.get('users') ) return ( <Grid rowData={users.toJS()} columnDefs={columnDefs} /> ) }
此写法下UI会重新渲染且警告消失,但违背了使用createSelector的初衷。
问题原因与解决方案
1. 组件不更新的核心原因
你用useStore.getState()手动获取状态的方式不会让组件订阅Redux状态变化。useStore仅用于获取store实例,组件首次渲染时会取一次状态,但后续store更新时,组件不会自动重新执行useMemoizedUsers,因此始终拿到旧的users值,自然不会触发重新渲染。
createSelector本身没有问题,问题出在调用选择器的方式上。
2. 正确的使用方式:用useSelector调用memoized选择器
要让组件监听状态变化,必须用useSelector来调用你的memoizedUsers选择器,而非直接调用store.getState()。修改useMemoizedUsers如下:
import { useSelector } from 'react-redux'; // 替换useStore为useSelector import { createSelector } from '@reduxjs/toolkit' const selectGroup = (state) => state.group; const memoizedUsers = createSelector( [selectGroup], (group) => { return group.get('users').toJS(); } ) const useMemoizedUsers = () => { return useSelector(memoizedUsers); // 用useSelector包裹选择器 }
或者更简洁地直接在组件中使用:
const UserList = () => { const users = useSelector(memoizedUsers); // 直接用useSelector调用选择器 return ( <Grid rowData={users} columnDefs={columnDefs} /> ) }
这样做的好处:
useSelector会自动订阅状态变化,当memoizedUsers的结果更新时,组件会重新渲染createSelector的缓存机制依然生效:只有当selectGroup返回的Immutablegroup实例变化时,才会重新执行toJS()转换,避免不必要的数组创建
3. 关于其他写法的解释
- 直接
useSelector+toJS()有警告:因为每次调用toJS()都会生成一个新的数组引用,即使Immutable数据没有变化,useSelector会认为返回值不同,导致组件不必要重渲染。 - 取Immutable对象再在render里
toJS():这种写法没问题,因为Immutable对象本身是引用不变的(数据未更新时返回同一个实例),useSelector不会触发不必要重渲染。但如果有更复杂的状态计算(比如过滤、排序用户),createSelector的缓存能避免重复计算,这时候还是应该用createSelector+useSelector的组合。
内容的提问来源于stack exchange,提问作者user2456977
相关产品推荐
相关产品推荐

