重新选择多实例记忆

Reselect multiple instance memoization

我使用 React/Redux/Reselect.

reselect doc 开始,如果我有多个组件将使用具有不同参数的选择器,我需要为每个实例创建一个。

const makeGetVisibleTodos = () => {
  return createSelector(
    [ 
       (state, props) => props.visibilityFilter, 
       state => state.todos,
    ],
    (visibilityFilter, todos) => {
       return todos.filter(td => td.visibility === visibilityFilter)
      }
    }
  )
}

但在我的例子中,listId 可能来自多个来源(例如 props.listIdprops.location.match.listIdprops.location.search.listId 等)

所以我更喜欢这样写:

const makeGetVisibleTodos = listId => {
  return createSelector(
    [
      state => state.todos,
    ],
    (todos) => {
       todos.filter(td => td.listId === listId)
      }
    }
  )
}

并按以下方式连接:

  connect(
    (state, ownProps) => ({
      offerer: makeGetVisibleTodos(ownProps.location.match.listId)(state, ownProps),
    }),
  )

我很确定它可以工作,但我不是 100% 确定它会正确记忆:

如果两个组件用相同的 listId 调用 makeGetVisibleTodos,它们将有 2 个不同的缓存值,对吗?这不是我想要的...

这个怎么样?

const makeGetVisibleTodos = listId => {
  return createSelector(
    state => state.todos,
    state => listId,
    (todos, listId) => {
       todos.filter(td => td.listId === listId)
      }
    }
  )
}

在这种情况下,makeGetVisibleTodos(ownProps.listId)makeGetVisibleTodos(ownProps.match.params.listId)共享相同的缓存值,当ownProps.listId === ownProps.match.params.listId?

或者另一种表达问题的方法是:如何传递不会(直接)依赖于 stateownProps 但在记忆过程中检查是否相等的额外参数?

我还可以扩展 ownProps:

  connect(
    (state, ownProps) => ({
      offerer: makeGetVisibleTodos()(state, Object.assign({}, ownProps, {listId: ownProps.location.match.listId}),
    }),
  )

但是觉得超级丑而且没重点了...

您的方法不会正确地进行记忆,因为当您调用 makeGetVisibleTodos 时总是会创建一个新的选择器。你可以通过写

来改进它
const listIdQuerySelector = (state, props) => {
    return props.match.params && props.match.params.listId;
};
const todoSelector = createSelector(
    [
      listIdQuerySelector,
      state => state.todos,
    ],
    (listId, todos) => {
       todos.filter(td => td.listId === listId)
      }
    }
  )

并像

一样使用它
connect(
    (state, ownProps) => ({
      offerer: makeGetVisibleTodos(state, ownProps),
    }),
  )

在您上面发布的示例中,记忆将无法正常工作,因为您每次调用 mapStateToProps 函数时都会创建一个重新选择选择器的新实例。

请注意文档中的 mapStateToProps 函数如何也成为工厂函数 (makeMapStateToProps) 并且 makeGetVisibleTodos 在返回实际的 mapStateToProps 函数之前被调用,例如所以。

const makeMapStateToProps = () => {
  const getVisibleTodos = makeGetVisibleTodos()
  const mapStateToProps = (state, props) => {
    return {
      todos: getVisibleTodos(state, props)
    }
  }
  return mapStateToProps
}

此外,您不会在 makeGetVisibleTodos() 调用中传递您在选择器中使用的道具,而是在实际调用选择器本身时传递。

这会导致类似

的结果
const makeGetVisibleTodos = () => {
  return createSelector(
    state => state.todos,
    state => (_, listId), // Here you have access to all the arguments passed
    (todos, listId) => {
       todos.filter(td => td.listId === listId)
      }
    }
  )
}

然后你可以这样写选择器:

const makeMapStateToProps = () => {
  const getVisibleTodos = makeGetVisibleTodos()
  const mapStateToProps = (state, props) => {

    return {
      todos: getVisibleTodos(state, props.params.match.listId),
      todos2: getVisibleTodos(state, props.listId),
      todos3: getVisibleTodos(state, 'hardcodedListId')
    }
  }
  return mapStateToProps
}

作为 re-reselectreselect 的小包装)的作者,我想指出库将如何解决您的用例。

re-reselect 提供开箱即用的选择器,它保留 跨不同组件的记忆

import createCachedSelector from 're-reselect';

const getVisibleTodos = createCachedSelector(
  state => state.todos,
  (state, listId) => listId,
  (todos, listId) => {
     todos.filter(td => td.listId === listId)
    }
  }
)(
  (todos, listId) => listId, // Create/use a different selector for each different listId
);

像普通选择器一样使用它(在任何不同的容器组件中)而忘记选择器工厂:

const makeMapStateToProps = () => {
  const mapStateToProps = (state, props) => {
    return {
      todos: getVisibleTodos(state, props.params.match.listId),
      todos2: getVisibleTodos(state, props.listId),
      todos3: getVisibleTodos(state, 'hardcodedListId')
    }
  }
  return mapStateToProps
}

此用例也在 re-reselect docs 中描述。