Redux 项目中的选择器(Selectors)使用指南:数据派生与性能优化
redux 项目地址: https://gitcode.com/gh_mirrors/red/redux
什么是选择器?
在 Redux 应用中,选择器(Selector)是一种用于从 Redux 状态树中提取和派生数据的函数。选择器的主要作用是封装状态访问逻辑,提高代码的可维护性,并通过记忆化(memoization)优化性能。
为什么需要选择器?
最小化状态原则
Redux 推崇最小化状态的设计理念,即只存储最原始的数据,其他派生数据都通过计算得到。例如:
- 待办事项应用存储原始任务列表,过滤后的列表通过计算获得
- 购物车存储商品条目,总价通过计算获得
这种设计带来三大优势:
- 状态树更简洁易读
- 减少维护派生数据一致性的逻辑
- 原始数据始终保持不变,便于追溯
React 中的相同原则
这一原则同样适用于 React 本地状态管理。很多开发者习惯在 useEffect
中计算派生状态并设置到 state 中,但实际上更好的做法是在渲染过程中直接计算:
function TodoList() {
const [todos] = useState([])
// 直接在渲染时计算派生值
const allCompleted = todos.every(todo => todo.completed)
// 使用该值进行渲染
}
选择器基础
基本概念
选择器函数接收整个 Redux 状态(或部分状态)作为参数,返回基于该状态的派生数据。选择器没有特殊格式要求,以下都是有效的选择器:
// 直接取值
const selectProducts = state => state.products
// 数组映射
const selectProductIds = state =>
state.products.map(product => product.id)
// 深层取值
const selectUserProfile = state =>
state.user.profile
命名规范
建议选择器名称以 select
开头,后接描述性内容,例如:
selectUserById
selectCompletedTodos
selectVisibleProducts
使用场景
选择器可用于:
useSelector
钩子connect
的mapState
- Redux 中间件
- Thunk 和 Saga
// 在 thunk 中使用选择器
function addToCart(productId) {
return (dispatch, getState) => {
const state = getState()
const canAdd = selectCanAddToCart(state)
if (canAdd) {
dispatch(cartAdded(productId))
}
}
}
选择器的封装价值
状态形状封装
直接访问深层状态会导致代码脆弱:
// 脆弱写法 - 直接访问深层状态
const data = useSelector(state => state.some.deeply.nested.field)
使用选择器封装后,状态结构调整只需修改选择器:
// 封装后的选择器
const selectSomeData = state => state.some.deeply.nested.field
// 组件中使用
const data = useSelector(selectSomeData)
最佳实践:只有 reducer 和选择器应该知道确切的状态结构。
记忆化选择器
性能问题
原始选择器存在两个性能隐患:
- 每次 action 后都会重新计算,即使相关状态未变化
- 总是返回新引用,导致不必要的组件重渲染
// ❌ 性能问题:总是返回新数组
const completedTodos = useSelector(state =>
state.todos.map(todo => todo.completed))
Reselect 解决方案
Reselect 库提供 createSelector
创建记忆化选择器:
import { createSelector } from 'reselect'
const selectTodos = state => state.todos
// 记忆化选择器
const selectCompletedTodos = createSelector(
[selectTodos], // 输入选择器
todos => todos.filter(todo => todo.completed) // 转换函数
)
工作原理:
- 缓存输入选择器的结果
- 只有输入变化时才重新计算
- 输入相同时返回缓存结果
高级用法
多参数选择器
const selectItems = state => state.items
const selectCategory = (state, category) => category
const selectItemsByCategory = createSelector(
[selectItems, selectCategory],
(items, category) => items.filter(item => item.category === category)
)
// 使用
const electronics = selectItemsByCategory(state, 'electronics')
选择器工厂
当需要创建多个独立缓存的选择器实例时:
const makeSelectItemsByCategory = () =>
createSelector(
[selectItems, selectCategory],
(items, category) => items.filter(item => item.category === category)
)
// 不同组件使用不同实例
const selectElectronics = makeSelectItemsByCategory()
const selectClothing = makeSelectItemsByCategory()
最佳实践
- 保持选择器纯净:不产生副作用
- 输入选择器应简单:只负责取值
- 复杂逻辑放在输出选择器:转换和计算
- 避免
state => state
:这会破坏记忆化 - 合理组织选择器:与相关 reducer 放在一起
// ✅ 良好实践
const selectUser = state => state.user
const selectOrders = state => state.orders
const selectUserOrders = createSelector(
[selectUser, selectOrders],
(user, orders) => orders.filter(o => o.userId === user.id)
)
通过遵循这些原则,您可以构建出高效、可维护的 Redux 状态选择系统,显著提升应用性能。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考