重选:createSelector不能正常工作



我的记忆选择器有一个问题。

阅读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与@reduxjs/toolkit
  • 我有一个待办事项列表
  • 我创建了一个"todoAdapter"将列表作为实体管理
  • 我想使用memoized selected来更新单个todo;无需重新渲染整个列表来优化我的应用。

但是…

当我使用"adapter.updateOne"调度更新时,标准选择器"SelectAll"每一次(我认为)改变参考

例子我有这个选择器从"切片">

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

如果我创建这个选择器

export const selectIdsCustom = createSelector(
selectTodosIDs,
(ids) => {
console.log('execute output function');
return ....
}
)

一切正常(state.todos)。id变化不明显)。

如果我创建这个选择器:

export const selectTodosCustom = createSelector(
selectAll,
(todos) => {
console.log('execute output function');
return ....
}
)

selectTodosCustom run "always"

Whyyy ? ?

对于updateOne,我只修改"state.todos.entities">

我错在哪里??我不明白的是什么?

我的应用程序只是一个案例研究。完整的应用程序已经打开:https://codesandbox.io/s/practical-hermann-60i7i

我只在typescript中创建了相同的应用程序,当我发现我的应用程序有这个问题时:

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

但我也有一些问题在官方的例子!!!!!

问题是我的嫉妒?某个库版本?

当我使用" adapter. updateone "调度更新时选择"SelectAll"每一次(我认为)改变参考

是的,你是对的。这是正确的。从@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 not change ref and In "entities"我正在换一个。

这是不对的??

不,在使用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])
);
...

最新更新