先学习useSelector的含义与用法
useSelector
是 React-Redux 库提供的一个钩子(Hook),它允许函数组件从 Redux store 中提取数据。它是 connect
函数的替代方案,用于在 React 组件中访问 Redux 的状态。
useSelector
的作用:
-
它接收一个 选择器函数 作为参数。
-
选择器函数会接收到整个 Redux 的状态(state),并返回组件需要的部分状态。
-
当 Redux store 更新时,
useSelector
会重新运行选择器函数。如果返回值发生变化,组件会重新渲染。
基本用法:
import { useSelector } from 'react-redux';
const MyComponent = () => {
// 从 Redux store 中提取数据
const data = useSelector((state) => state.someReducer.someValue);
return (
<div>
<p>{data}</p>
</div>
);
};
关键点:
-
选择器函数:
-
选择器函数应该是纯函数,即相同的输入总是返回相同的输出。
-
它可以返回任何值:基本类型、对象或数组。
-
-
相等性检查:
-
默认情况下,
useSelector
使用严格相等(===
)来比较选择器函数返回的值。 -
如果值没有变化,组件不会重新渲染。
-
如果需要,可以通过第二个参数传入自定义的相等性检查函数。
-
-
性能优化:
-
避免在选择器函数中创建新的对象或数组,这可能导致不必要的重新渲染。
-
对于复杂的状态计算,可以使用记忆化的选择器(例如
reselect
库)。
-
多个选择器的例子:
import { useSelector } from 'react-redux';
const UserProfile = () => {
const user = useSelector((state) => state.user);
const posts = useSelector((state) => state.posts);
return (
<div>
<h1>{user.name}</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};
自定义相等性检查的例子:
import { useSelector, shallowEqual } from 'react-redux';
const MyComponent = () => {
const { firstName, lastName } = useSelector(
(state) => ({
firstName: state.user.firstName,
lastName: state.user.lastName,
}),
shallowEqual // 确保只有在 firstName 或 lastName 变化时才重新渲染
);
return (
<div>
<p>{firstName} {lastName}</p>
</div>
);
};
什么时候使用 useSelector
:
-
当你使用函数组件时。
-
当你希望用更简洁的方式访问 Redux store,而不是使用
connect
。 -
当你需要更细粒度地控制组件的重新渲染。
shallowEqual 的作用
shallowEqual
的实现原理
shallowEqual
的实现逻辑大致如下:
-
比较两个值的引用是否相同(
===
)。 -
如果两个值都是对象或数组,遍历它们的属性或元素,逐一比较。
-
如果所有属性或元素的值都相等(
===
),返回true
;否则返回false
。
伪代码:
function shallowEqual(objA, objB) {
if (objA === objB) return true; // 引用相同
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
return false; // 不是对象或数组
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) return false; // 属性数量不同
for (let key of keysA) {
if (objA[key] !== objB[key]) return false; // 属性值不同
}
return true;
}
注意事项
-
浅比较的局限性:
-
shallowEqual
只能比较第一层属性或元素,如果对象或数组嵌套较深,可能需要手动优化。
-
-
复杂状态:对于复杂的状态计算,建议使用
在这个例子中:reselect
库创建记忆化的选择器。-
const user = useSelector( (state) => ({ firstName: state.user.firstName, lastName: state.user.lastName, }), shallowEqual // 使用浅比较 );
为什么
shallowEqual
会返回true
? -
对象结构简单:
-
选择器返回的对象只有一层结构,即
{ firstName, lastName }
。 -
shallowEqual
只会比较这两个属性的值(firstName
和lastName
),而不会深入比较嵌套对象。
-
-
浅比较的逻辑:
-
如果
state.user.firstName
和state.user.lastName
的值没有变化,那么shallowEqual
会认为这两个对象是相等的,返回true
。 -
由于
shallowEqual
返回true
,useSelector
会认为状态没有变化,因此组件不会重新渲染。
-
-
具体场景分析
假设 Redux store 的状态如下:
state = { user: { firstName: 'John', lastName: 'Doe', age: 30, // 其他属性 }, posts: [], // 其他状态 };
场景 1:
firstName
或lastName
变化
-
如果
state.user.firstName
从'John'
变为'Jane'
,选择器返回的对象会变成{ firstName: 'Jane', lastName: 'Doe' }
。 -
shallowEqual
会检测到firstName
的变化,返回false
,组件会重新渲染。
场景 2:
age
变化-
如果
state.user.age
从30
变为31
,但firstName
和lastName
未变,选择器返回的对象仍然是{ firstName: 'John', lastName: 'Doe' }
。 -
shallowEqual
会检测到对象内容未变,返回true
,组件不会重新渲染。
场景 3:其他状态变化
-
如果
state.posts
发生变化,但state.user.firstName
和state.user.lastName
未变,选择器返回的对象仍然是{ firstName: 'John', lastName: 'Doe' }
。 -
shallowEqual
会检测到对象内容未变,返回true
,组件不会重新渲染。 -
为什么适合用
shallowEqual
?在这个例子中,选择器返回的对象非常简单,只有一层结构(
firstName
和lastName
)。shallowEqual
只需要比较这两个属性的值,因此非常适合这种场景。如果对象结构更复杂(例如嵌套对象或数组),
shallowEqual
可能无法满足需求,这时需要使用其他优化手段(如reselect
或手动深度比较)。
-
*******拓展***** 避免在选择器函数中创建新对象/数组
问题场景:直接返回新对象或数组,即使数据未变,也会触发重新渲染。
优化方法:返回基本类型值,或使用浅比较(shallowEqual
)。
错误示例 ❌
const UserInfo = () => {
// ❌ 错误:每次返回新对象,即使 firstName 和 lastName 未变
const user = useSelector((state) => ({
firstName: state.user.firstName,
lastName: state.user.lastName,
}));
return <div>{user.firstName} {user.lastName}</div>;
};
即使 firstName
和 lastName
未变,每次 state
其他部分变化时,user
对象都是新的引用,组件会重新渲染。
正确示例 ✅
import { useSelector, shallowEqual } from 'react-redux';
const UserInfo = () => {
// ✅ 正确:使用 shallowEqual 比较对象内容
const { firstName, lastName } = useSelector(
(state) => ({
firstName: state.user.firstName,
lastName: state.user.lastName,
}),
shallowEqual // 浅比较对象内容
);
return <div>{firstName} {lastName}</div>;
};
或直接提取单个值:
const UserInfo = () => {
// ✅ 正确:分别提取值,避免返回对象
const firstName = useSelector((state) => state.user.firstName);
const lastName = useSelector((state) => state.user.lastName);
return <div>{firstName} {lastName}</div>;
};