redux中的hooks使用详解
作者:改了一个昵称
useSelector从Redux提取状态,配合shallowEqual和memo优化渲染性能;Reselect通过缓存减少重复计算,适用于复杂选择器逻辑,提升效率
useSelector
基础使用
- 功能:
useSelector是 react-redux 提供的 Hook,用于从 Redux Store 中选择(提取)状态数据 - 参数一:接收一个 选择器函数,该函数接收完整的
Redux Store 状态(state)作为参数,返回需要的数据 - 参数二: 可传入
shallowEqual,比较来决定组件是否重新渲染 - 返回值:返回 选择器函数的结果(这里是 { count: … } 对象)
import { useSelector } from 'react-redux'
import { memo } from 'react'
const App = memo(() => {
` 使用 useSelector 将 Redux Store 中的数据映射到组件内 `
` state 参数:代表整个 Redux Store 的根状态(root state) `
const { count } = useSelector((state) => {
return {
`假设你的 Redux Store 中有一个名为 counter 的 slice(分片),该 slice 的状态中包含 count 字段`
count: state.counter.count
}
})
return (
<div>
<h2>count: {count}</h2>
</div>
)
})
这里memo的作用:
- 用于对组件进行浅层 props 比较,避免不必要的重新渲染。
适用场景:
- 如果 App 组件的父组件频繁渲染,但 App 的 props 未变化,memo 可以优化性能。
memo 与 useSelector 的关系:
- 即使 memo 包裹了组件,useSelector 仍会在 Redux Store 的 count 变化时触发组件更新(因为内部状态变化)。
第二个参数 shallowEqual
- 功能:
shallowEqual是 react-redux 提供的浅比较函数,用于,比较两次选择器返回的结果,是否 “浅层相等”。 - 适用场景:当你的选择器返回一个对象或数组时,shallowEqual 会逐个比较对象的每个属性(或数组的每个元素)是否严格相等(===)。
- 深层嵌套无效:
shallowEqual 仅比较 第一层属性。如果对象有深层嵌套(如 { data: { count: 1 } }),深层变化不会被检测到。

父组件修改count时,App依赖的count发生改变,所以,父组件App会重新渲染,
但是,此时子组件也重新渲染了,子组件并没有依赖count,而且子组件此时是用memo包裹的,memo包裹的组件只有当其props发生改变时,才会重新渲染。
当子组件修改msg时,App组件也重新渲染了。
这是因为使用了useSelector,useSelector 监听的是 整个state数据,如果state里有任何数据发生变化,当前组件就会重新渲染。
而正常情况下,应该是:
- 只有 msg 改变时,子组件才会渲染;
- 只有count改变时,父组件才会渲染。
useSelector的第二个参数:用来比较来决定组件是否需要渲染,当state发生改变时,useSelector 将本次映射的值与上次映射的值有没有发生改变,有改变则重新渲染,没有改变则不变。
// state发生变化时,父组件的useSelector判断本次映射的count与上次映射的count是否一致,不一致则重新渲染,否则不重新渲染
const { count } = useSelector((state) => {
return {
count: state.counter.count
}
}, shallowEqual)
// state发生变化时,子组件的useSelector判断本次映射的msg与上次映射的msg是否一致,不一致则重新渲染,否则不重新渲染
const { msg } = useSelector((state) => {
return {
msg: state.counter.msg
}
}, shallowEqual)
Reselect
Reselect 是一个专为 Redux 设计的记忆化选择器库,用于优化从 Redux Store 中提取数据的性能。
它的核心思想是:通过缓存(memoization)避免重复计算,确保只有在相关状态变化时,才重新执行选择器逻辑。
(1) 什么是记忆化(Memoization)?
- 定义:记忆化是一种优化技术,用于缓存函数的输入和输出。当函数以相同的输入再次调用时,直接返回缓存的结果,而不是重新计算。
- 目的:在 Redux 中,当状态频繁变化但某些派生数据未变化时,避免重复计算这些派生数据,从而提升性能。
(2) Reselect 的作用
场景:
- 当你的选择器逻辑复杂(如:组合`个状态片段、进行计算)时,Reselect 会自动缓存结果。
优势:
- 减少不必要的计算。
- 确保组件只在相关状态变化时重新渲染。
- 保持选择器逻辑的纯净(无副作用)。
Reselect 的核心 API:createSelector
用法:
import { createSelector } from 'reselect'
`输入选择器:从状态中提取原始数据`
const selectUser = (state) => state.user 【state.user 是 Redux Store 中的一个 slice】
const selectPosts = (state) => state.posts 【state.posts 是 Redux Store 中的一个 slice】
`Reselect 的 createSelector 会按顺序将 输入选择器的返回值,作为参数传递给转换函数。
因此,转换函数中的
user 对应 selectUser的返回值(即state.user)
posts 对应 selectPosts的返回值(即state.posts)`
const selectUserData = createSelector(
[selectUser, selectPosts], `【输入选择器数组】`
(user, posts) => ({ `【转换函数】`
username: user.name,
postCount: posts.length,
})
)
某组件如下:
import { useSelector } from 'react-redux'
import { selectUserData } from './selectors'
const Component = () => {
const { username, postCount } = useSelector(selectUserData)
return (
<div>
<h3>{username}</h3>
<p>Posts: {postCount}</p>
</div>
)
}
Reselect的缓存机制- 缓存触发条件:只有当
selectUser或selectPosts的返回值变化时,转换函数才会重新执行。
执行流程:
首次调用:
- 执行所有输入选择器(
selectUser和selectPosts)。 - 将结果传递给转换函数,计算并缓存最终值。
后续调用:
- 再次执行输入选择器。
- 如果所有输入选择器的结果与上一次相同(严格相等
===),直接返回缓存值。 - 否则,重新执行转换函数并更新缓存。
结合shallowEqual
- 如果选择器返回对象,可以结合
shallowEqual进一步优化:
const { username, postCount } = useSelector(
selectUserData,
shallowEqual
)
高级用法:组合选择器
多层选择器
const selectUser = (state) => state.user
const selectPosts = (state) => state.posts
const selectUserData = createSelector(
[selectUser, selectPosts],
(user, posts) => ({
username: user.name,
postCount: posts.length,
})
)
const selectUserStats = createSelector(
[selectUserData],
(userData) => ({
avgPostsPerDay: userData.postCount / 30,
})
)
带参数的选择器
const selectPostById = (postId) => createSelector(
[(state) => state.posts],
(posts) => posts.find(post => post.id === postId)
)
// 组件中使用
const Component = ({ postId }) => {
const post = useSelector(() => selectPostById(postId))
return <div>{post.title}</div>
}
深入解析:Reselect 的工作原理
(1) 输入选择器的顺序
输入选择器的顺序会影响缓存键的计算。例如:
const selectorA = createSelector( [selectX, selectY], (x, y) => x + y ); const selectorB = createSelector( [selectY, selectX], (y, x) => y + x );
selectorA和selectorB的缓存键不同(顺序不同),即使逻辑相同也会重新计算。
(2) 缓存的生命周期
- 全局缓存:Reselect 的缓存是全局的,与组件实例无关。
- 状态变化时:只要输入选择器的结果变化,缓存就会失效。
(3) 深层嵌套状态的处理
- 如果状态是深层嵌套的对象,输入选择器需要提取具体的字段:
const selectDeepData = createSelector( [(state) => state.deep.nested.data], (data) => data.value );
总结:
Reselect 的本质:通过记忆化选择器,避免重复计算,优化性能。
适用场景:当选择器逻辑复杂、依赖多个状态片段或需要组合数据时。
最佳实践:
- 优先使用
createSelector创建选择器。 - 保持输入选择器的简单性,将复杂逻辑放在转换函数中。
- 结合
useSelector和shallowEqual进一步优化渲染性能。
useDispatch获取dispatch函数
- 直接用
useDispatch获取 dispatch ,派发action 即可
` 派发操作 `
const dispatch = useDispatch()
function calNumber (num) {
dispatch(addNumberAction(num))
}
return (
<div>
<h2>count:{count}</h2>
<button onClick={e => calNumber(1)}> +1 </button>
</div>
)
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
