useSelector的第二个参数shallowEqual

 先学习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>
  );
};
 

关键点:

  1. 选择器函数

    • 选择器函数应该是纯函数,即相同的输入总是返回相同的输出。

    • 它可以返回任何值:基本类型、对象或数组。

  2. 相等性检查

    • 默认情况下,useSelector 使用严格相等(===)来比较选择器函数返回的值。

    • 如果值没有变化,组件不会重新渲染。

    • 如果需要,可以通过第二个参数传入自定义的相等性检查函数。

  3. 性能优化

    • 避免在选择器函数中创建新的对象或数组,这可能导致不必要的重新渲染。

    • 对于复杂的状态计算,可以使用记忆化的选择器(例如 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 的实现逻辑大致如下:

  1. 比较两个值的引用是否相同(===)。

  2. 如果两个值都是对象或数组,遍历它们的属性或元素,逐一比较。

  3. 如果所有属性或元素的值都相等(===),返回 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;
}

注意事项

  1. 浅比较的局限性

    • shallowEqual 只能比较第一层属性或元素,如果对象或数组嵌套较深,可能需要手动优化。

  2. 复杂状态:对于复杂的状态计算,建议使用 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 返回 trueuseSelector 会认为状态没有变化,因此组件不会重新渲染。


    • 具体场景分析

      假设 Redux store 的状态如下:

      state = {
        user: {
          firstName: 'John',
          lastName: 'Doe',
          age: 30, // 其他属性
        },
        posts: [], // 其他状态
      };

      场景 1:firstName 或 lastName 变化
    1. 如果 state.user.firstName 从 'John' 变为 'Jane',选择器返回的对象会变成 { firstName: 'Jane', lastName: 'Doe' }
    2. 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>;
};

                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值