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> )
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。