You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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返回的Immutable group实例变化时,才会重新执行toJS()转换,避免不必要的数组创建

3. 关于其他写法的解释

  • 直接useSelector+toJS()有警告:因为每次调用toJS()都会生成一个新的数组引用,即使Immutable数据没有变化,useSelector会认为返回值不同,导致组件不必要重渲染。
  • 取Immutable对象再在render里toJS():这种写法没问题,因为Immutable对象本身是引用不变的(数据未更新时返回同一个实例),useSelector不会触发不必要重渲染。但如果有更复杂的状态计算(比如过滤、排序用户),createSelector的缓存能避免重复计算,这时候还是应该用createSelector+useSelector的组合。

内容的提问来源于stack exchange,提问作者user2456977

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 05:44:56