重新选择不会正确记忆同一组件的多个实例
Reselect will not correctly memoize with multiple instances of the same component
我正在阅读 Redux 的文档,但遇到了 reselect
。下面的代码创建了一个选择器,文档说,如果我们想在两个 VisibleTodoList
组件中使用它,那么它将无法 正确地工作 。
import { createSelector } from 'reselect'
const getVisibilityFilter = (state, props) => state.todoLists[props.listId].visibilityFilter
const getTodos = (state, props) => state.todoLists[props.listId].todos
const getVisibleTodos = createSelector([getVisibilityFilter, getTodos], (visibilityFilter, todos) => {
switch (visibilityFilter) {
case 'SHOW_COMPLETED':
return todos.filter(todo => todo.completed)
case 'SHOW_ACTIVE':
return todos.filter(todo => !todo.completed)
default:
return todos
}
})
export default getVisibleTodos
Using the getVisibleTodos selector with multiple instances of the visibleTodoList container will not correctly memoize
const mapStateToProps = (state, props) => {
return {
// WARNING: THE FOLLOWING SELECTOR DOES NOT CORRECTLY MEMOIZE
todos: getVisibleTodos(state, props)
}
}
这是什么意思?我不明白为什么它不起作用。
正确。这是因为默认情况下,Reselect 仅记忆最近的一组输入:
const a = someSelector(state, 1); // first call, not memoized
const b = someSelector(state, 1); // same inputs, memoized
const c = someSelector(state, 2); // different inputs, not memoized
const d = someSelector(state, 1); // different inputs from last time, not memoized
在这些情况下,选择器仍会检索数据,它只需要重新计算结果,即使它在过去的某个时间点看到了输入。
因此,如果您在 mapState
函数中使用选择器,并且它引用了 ownProps
中的值,那么组件的多个实例可能会导致选择器永远无法正确记忆
const mapState = (state, ownProps) => {
const item = selectItemForThisComponent(state, ownProps.itemId);
return {item};
}
// later
<SomeComponent itemId={1} />
<SomeComponent itemId={2} />
在那个例子中,selectItemForThisComponent
总是会连续调用 (state, 1)
和 (state, 2)
,所以它不会正确记忆。
一种解决方案是使用 connect
支持的 "factory function" 语法。如果您的 mapState
函数 returns 是第一次被调用的函数,connect
将使用它作为真正的 mapState
实现。这样,您可以为每个组件实例创建唯一的选择器:
const makeUniqueSelectorInstance = () => createSelector(
[selectItems, selectItemId],
(items, itemId) => items[itemId]
);
const makeMapState = (state) => {
const selectItemForThisComponent = makeUniqueSelectorInstance();
return function realMapState(state, ownProps) {
const item = selectItemForThisComponent(state, ownProps.itemId);
return {item};
}
}
export default connect(makeMapState)(SomeComponent);
组件 1 和组件 2 都将获得自己独特的 selectItemForThisComponent
副本,并且每个副本都将使用一致可重复的输入进行调用,从而实现适当的记忆。
更新
我在我的博客 post Idiomatic Redux: Using Reselect Selectors for Performance and Encapsulation.
中扩展了这个答案
我正在阅读 Redux 的文档,但遇到了 reselect
。下面的代码创建了一个选择器,文档说,如果我们想在两个 VisibleTodoList
组件中使用它,那么它将无法 正确地工作 。
import { createSelector } from 'reselect'
const getVisibilityFilter = (state, props) => state.todoLists[props.listId].visibilityFilter
const getTodos = (state, props) => state.todoLists[props.listId].todos
const getVisibleTodos = createSelector([getVisibilityFilter, getTodos], (visibilityFilter, todos) => {
switch (visibilityFilter) {
case 'SHOW_COMPLETED':
return todos.filter(todo => todo.completed)
case 'SHOW_ACTIVE':
return todos.filter(todo => !todo.completed)
default:
return todos
}
})
export default getVisibleTodos
Using the getVisibleTodos selector with multiple instances of the visibleTodoList container will not correctly memoize
const mapStateToProps = (state, props) => {
return {
// WARNING: THE FOLLOWING SELECTOR DOES NOT CORRECTLY MEMOIZE
todos: getVisibleTodos(state, props)
}
}
这是什么意思?我不明白为什么它不起作用。
正确。这是因为默认情况下,Reselect 仅记忆最近的一组输入:
const a = someSelector(state, 1); // first call, not memoized
const b = someSelector(state, 1); // same inputs, memoized
const c = someSelector(state, 2); // different inputs, not memoized
const d = someSelector(state, 1); // different inputs from last time, not memoized
在这些情况下,选择器仍会检索数据,它只需要重新计算结果,即使它在过去的某个时间点看到了输入。
因此,如果您在 mapState
函数中使用选择器,并且它引用了 ownProps
中的值,那么组件的多个实例可能会导致选择器永远无法正确记忆
const mapState = (state, ownProps) => {
const item = selectItemForThisComponent(state, ownProps.itemId);
return {item};
}
// later
<SomeComponent itemId={1} />
<SomeComponent itemId={2} />
在那个例子中,selectItemForThisComponent
总是会连续调用 (state, 1)
和 (state, 2)
,所以它不会正确记忆。
一种解决方案是使用 connect
支持的 "factory function" 语法。如果您的 mapState
函数 returns 是第一次被调用的函数,connect
将使用它作为真正的 mapState
实现。这样,您可以为每个组件实例创建唯一的选择器:
const makeUniqueSelectorInstance = () => createSelector(
[selectItems, selectItemId],
(items, itemId) => items[itemId]
);
const makeMapState = (state) => {
const selectItemForThisComponent = makeUniqueSelectorInstance();
return function realMapState(state, ownProps) {
const item = selectItemForThisComponent(state, ownProps.itemId);
return {item};
}
}
export default connect(makeMapState)(SomeComponent);
组件 1 和组件 2 都将获得自己独特的 selectItemForThisComponent
副本,并且每个副本都将使用一致可重复的输入进行调用,从而实现适当的记忆。
更新
我在我的博客 post Idiomatic Redux: Using Reselect Selectors for Performance and Encapsulation.
中扩展了这个答案