Reselect 相较于连接式容器组件的优势有哪些?
你提到的把计算逻辑放在与store连接的容器组件里的方案,在简单场景下确实能工作,但reselect(尤其是它的createSelector())有几个核心优势,让它成为Redux生态中处理派生数据的优先选择,具体来说:
1. 记忆化缓存,避免不必要的计算
createSelector()会自动对计算结果进行记忆化——只有当它依赖的state切片(比如例子里的todos和visibilityFilter)发生变化时,才会重新执行计算逻辑。而你给出的容器组件方案中,虽然connect()会在相关state变化时才更新容器,但每次容器渲染时,getVisibleTodos都会被调用一次。如果计算逻辑复杂(比如处理大量数据、多层过滤转换),或者后续mapStateToProps返回了更多无关字段,这种重复计算会带来不必要的性能损耗。reselect的记忆化能直接规避这个问题,提升应用性能。
2. 计算逻辑可复用
用createSelector()创建的选择器是独立的纯函数,可以轻松在多个组件、甚至其他选择器中复用。比如如果另一个组件也需要获取“可见todo列表”,直接导入这个选择器就行,不用重复复制getVisibleTodos的逻辑。而把计算逻辑写在容器组件的渲染函数里,逻辑就和组件绑定死了,复用起来只能复制粘贴,维护成本很高。
3. 保持容器组件的职责单一
容器组件的核心职责应该是连接Redux store和UI组件,把state和dispatch映射成组件props。如果把计算逻辑塞进容器的渲染函数里,会让容器变得臃肿,职责不清。reselect把派生数据的计算逻辑抽离成独立的选择器,容器只需要调用选择器获取结果,代码结构更清晰,也更符合“单一职责”原则。
4. 贴合Redux社区最佳实践
Redux官方文档和社区长期以来都推荐用reselect处理派生数据,包括Redux Toolkit(现在官方主推的Redux开发工具)也内置了对reselect的支持。使用reselect的话,你的代码会更符合社区规范,团队协作时其他开发者也更容易理解和维护。
对比示例:用reselect重构你的代码
把你给出的代码改成reselect版本后,会更简洁且具备上述优势:
import { connect } from 'react-redux' import { toggleTodo } from '../actions' import TodoList from '../components/TodoList' import { createSelector } from 'reselect' // 基础选择器:获取state中的原始切片 const selectTodos = state => state.todos const selectVisibilityFilter = state => state.visibilityFilter // 记忆化选择器:计算派生数据 const selectVisibleTodos = createSelector( [selectTodos, selectVisibilityFilter], (todos, filter) => { switch (filter) { case 'SHOW_ALL': return todos case 'SHOW_COMPLETED': return todos.filter(t => t.completed) case 'SHOW_ACTIVE': return todos.filter(t => !t.completed) } } ) const mapStateToProps = state => { return { todos: selectVisibleTodos(state) } } const mapDispatchToProps = dispatch => { return { onTodoClick: id => { dispatch(toggleTodo(id)) } } } const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList) export default VisibleTodoList
总之,你的方案在简单场景下可以运行,但reselect在性能优化、逻辑复用、代码组织上都有明显的优势,这也是它成为官方推荐方案的核心原因。
内容的提问来源于stack exchange,提问作者S. I. Svendsen

