redux 重新选择:createSelector无法正常工作

x4shl7ld  于 2022-11-12  发布在  其他
关注(0)|答案(1)|浏览(149)

我的记忆选择器有问题。
在阅读https://redux.js.org/usage/deriving-data-selectors上的文档时,我截取了以下片段:

const state = {
  a: {
    first: 5
  },
  b: 10
}

const selectA = state => state.a
const selectB = state => state.b

const selectA1 = createSelector([selectA], a => a.first)

const selectResult = createSelector([selectA1, selectB], (a1, b) => {
  console.log('Output selector running')
  return a1 + b
})

const result = selectResult(state)
// Log: "Output selector running"
console.log(result)
// 15

const secondResult = selectResult(state)
// No log output
console.log(secondResult)
// 15

我的问题在于secondResult函数,记录结果。
这一切都是有一点前提的。
我的问题是:

  • 我正在使用react with @reduxjs/工具包
  • 我有一个待办事项清单
  • 我创建了一个“todoAdapter”来将列表作为实体进行管理
  • 我想使用memoized selected来更新一个“待办事项”,而不重新呈现整个列表来优化我的应用程序。

但是......
当我用“adapter.updateOne”分派更新时,标准选择器“SelectAll”每次(我认为)都更改ref。
范例
我从“slice”中找到了这个选择器

export const {
    selectAll,
    selectIds: selectTodosIDs,
    selectTotal: selectTodosCount,
    selectById: selectTodoById
} = todosSelector;

如果我创建这个选择器

export const selectIdsCustom = createSelector(
    selectTodosIDs,

    (ids) => {
        console.log('execute output function');
        return ....
    }
)

一切正常(状态.待办事项. id没有明显变化)。
如果创建此选择器:

export const selectTodosCustom = createSelector(
    selectAll,

    (todos) => {
        console.log('execute output function');
        return ....
    }
)

选择待办事项自定义运行“始终”。
为什么?
使用updateOne,我“仅“修改了“state.todos.entities”中的一个实体
我哪里错了??我不明白什么?
我的应用程序只是一个案例研究。完整的应用程序在:https://codesandbox.io/s/practical-hermann-60i7i
我只在typescript中创建了相同的应用程序,当我作为我的应用程序遇到这个问题时:

  • 我下载了上面的原始应用程序表单链接(官方redux示例)
  • npm安装
  • npm启动

不过我这有些问题也在官方的例子里!
问题出在我的env?某个库版本?

5gfr0r5j

5gfr0r5j1#

当我用“adapter.updateOne”分派更新时,标准选择器“SelectAll”每次(我认为)都更改ref。
是的,你是对的。它是正确的。来自@reduxjs/toolkitselectAll依赖于来自实体状态的idsentities

{
  ids: ['1', '2'],
  entities: {
    1: {
      id: '1',
      title: 'First',
    },
    2: {
      id: '2',
      title: 'Second',
    },
  },
}

每次使用adapter.updateOne调度更新时,对entities对象的引用都会发生变化。这是正常的,这就是immerjs(在reduxtoolkit的引擎盖下使用)如何提供正确的不变性:

dispatch(updateOne({ id: '1', changes: { title: 'First (altered)' } }));

const state = {
  ids: ['1', '2'],
  entities: { // <--- new object
    1: { // <--- new object
      id: '1',
      title: 'First (altered)',
    },
    2: { // <--- old object
      id: '2',
      title: 'Second',
    },
  },
};

如果entities对象仍然是旧的,则选择器selectAll将返回一个记忆值,其中第一个元素的标题不正确。
要优化列表的重新呈现(实际上只对大型列表有用),您应该在父组件中使用选择器selectIds,在子组件中使用选择器selectById

const Child = ({ id }) => {
  const dispatch = useDispatch();
  const book = useSelector((state) => selectById(state, id));

  const handleChange = useCallback(() => {
    // the parent component will not be re-render
    dispatch(
      bookUpdate({
        id,
        changes: {
          title: `${book.title} (altered)`,
        },
      })
    );
  }, [dispatch, id, book]);

  return (
    <div>
      <h2>{book.title}</h2>
      <button onClick={handleChange}>change title</button>
    </div>
  );
};

function Parent() {
  const ids = useSelector(selectIds);

  return (
    <div>
      {ids.map((id) => (
        <Child key={id} id={id}></Child>
      ))}
    </div>
  );
}

更新

在本例中,item没有改变ref,而在“实体”里面我改变了一个单独的道具。
这不对吗???
不,在使用redux时不能这样做。Redux是基于不变性的思想。任何状态的改变都会创建一个新的状态对象。更新实体只是状态对象的一个深层改变。并且在更新元素的路径上的所有对象都必须是新的。如果你不遵守这个规则,那么基本工具将不能正常工作。所有这些工具默认使用严格比较。
但是,即使选择器返回具有不同引用的相等数组,您仍然可以避免重新呈现组件。只需传递您自己的比较函数:

...  
const todoIds = useSelector(
    selectFilteredTodoIds, 
   // the component will be re-render only if this function returns false
    (next, prev) => next.every((id, index) => id === prev[index])
);
...

相关问题