zustand的useShallow hook到底有什么优势?

zustand的useShallow hook到底有什么优势?

使用zustand获取状态大概有三种:

  • const store = useZustand()
  • const {xxx} = useZustand(state => state.xxx)。
  • const {xxx} = useZustand(useShallow(stste => state.xxx))

第一种因为会在组件导入所有状态,会导致组件频繁re-render 所以不建议使用。但是zustand文档中建议使用的useShallow 我始终不理解它到底有什么优势

第二种获取状态的方式叫做 selector,selector和useShallow的区别 文档中介绍说是 selector比对采用严格比较 就是===,而useShallow是浅层比较 只比较状态的引用 也就是==。但是这两种我觉得在zustand里没有任何区别。因为zustand在修改一个对象类型状态时 都是会返回一个新对象 而不是直接修改这个对象本身。那么严格比较和浅层比较就没有任何意义了 因为每次状态的引用都会发生变化。请教大神 哪些情况下 是必须使用useShallow的?
我在这里抛砖引玉下:

// store.js
const useZustand = create((set) => {
    userinfo: {
        name: '',
        sex: ''
    },
    setUserinfo: (payload) => {
        set(state => ({...state.userinfo, ...payload}))
    }
})

现在我有这么一个store,然后我在组件内使用:

// index.jsx
import useZustand from './store.js'
import {useShallow} from 'zustand/react/shallow'
// 这两种写法 都能达到只有userinfo变化时 重新渲染组件。但是还有其他差别吗?
const userinfo = useZustand(state => state.userinfo)
const userinfo = useZustand(useShallow(state => state.userinfo))

我在react中 找不出 严格比较和浅层比较的区别。因为都是返回新的对象 就算是 对象嵌套对象。这种情况下 我分别测试 selector和useShallow 发现两者表现并无区别。我想知道 有哪些情况下 必须使用useShallow?

阅读 2.4k
avatarAI BotBETA

zustanduseShallow hook 的优势主要在于其性能优化和简化深层嵌套状态的更新监听。尽管在你提供的简单例子中(如直接修改对象属性并返回新对象),useShallow 和普通的 selector(使用严格比较 ===)看起来没有区别,但在更复杂的场景中,useShallow 可以显著减少不必要的重新渲染。

useShallow 的优势

  1. 性能优化

    • 当状态是一个大型对象或嵌套对象时,使用 useShallow 可以避免因为深层状态未改变而引发的无效重新渲染。useShallow 只检查顶层对象的引用是否变化,如果顶层对象的引用没有变化(即使其内部属性或子对象发生了变化,但这些变化不影响顶层对象的引用),使用 useShallow 的组件将不会重新渲染。
    • 这对于性能敏感的应用或大型应用中的深层嵌套状态尤其有用。
  2. 简化状态管理

    • 在处理大型或复杂的状态结构时,手动编写 selector 来确保只监听特定子状态的变化可能会变得复杂且容易出错。useShallow 提供了一种简洁的方式来监听顶层对象的变化,而无需关心其内部的具体结构。

何时使用 useShallow

  • 大型或复杂的状态结构:当状态是一个大型对象或嵌套对象,且你只对顶层对象的变化感兴趣时。
  • 性能优化:在性能敏感的场景中,使用 useShallow 可以减少不必要的重新渲染,从而提高应用的性能。
  • 简化代码:当状态结构复杂,手动编写高效的 selector 变得困难时,useShallow 提供了一种简单而有效的替代方案。

示例说明

在你的示例中,由于 userinfo 是一个简单对象,并且每次更新都会返回一个新的对象引用,因此 useShallow 和普通的 selector 看起来没有区别。然而,如果 userinfo 是一个嵌套对象,并且你只关心顶层对象的变化(例如,整个 userinfo 对象被替换,但内部属性可能未改变),那么 useShallow 将只会在顶层对象引用变化时触发重新渲染,从而避免不必要的性能开销。

// 假设 userinfo 是一个更复杂的嵌套对象
const useZustand = create((set) => ({
  userinfo: {
    profile: {
      name: '',
      sex: ''
    },
    preferences: {
      // 更多嵌套属性
    }
  },
  setUserinfo: (payload) => {
    set(state => ({
      ...state.userinfo,
      profile: {...state.userinfo.profile, ...payload.profile}
    }))
  }
}));

// 使用 useShallow 监听 userinfo 顶层对象的变化
const userinfo = useZustand(useShallow(state => state.userinfo));

在这个更复杂的示例中,如果 preferences 对象发生变化,但 profile 对象保持不变(且整个 userinfo 对象的引用也未变化),使用 useShallow 的组件将不会重新渲染,因为顶层对象的引用没有变化。这可以显著提高性能,尤其是在大型应用中。

1 个回答
selector 比对采用严格比较,就是 ===;而 useShallow 是浅层比较 只比较状态的引用,也就是 ==。

首先这句话就不对。 zustand 比较是用的 Object.is 而不是 ===;而 useShallow 浅比较如果是数组,那就遍历 + Object.is;如果是对象,那就 Object.entries 后再遍历 + Object.is

为 zustand 在修改一个对象类型状态时,都是会返回一个新对象,而不是直接修改这个对象本身。那么严格比较和浅层比较就没有任何意义了,因为每次状态的引用都会发生变化。

其次你提到这种 immutable 的工作方式本身是对的,但结论不对。因为你没有考虑到一个场景:返回的这个新对象、其浅层属性依然等于原对象的各属性。比如原题里那个 userinfo,更新时你是 set 回去一个新的对象了,这个对象跟原对象的引用不同是没错,可若是这个新的 userinfo.name 和 userinfo.sex 和之前完全相同呢?


一个例子:传送门

P.S. React Playground 网站被墙,需要魔法才能访问,你懂的

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏