教程 zustand tutorial

性能优化:Selector 与 Shallow

在构建大型应用时,性能优化是一个关键考虑因素。Zustand 提供了强大的性能优化工具,其中最重要的就是 Selector(选择器)和 Shallow(浅比较)。

Selector:精确订阅状态

Selector 是一种从 Store 中选择特定状态的函数。通过使用 Selector,组件可以只订阅它真正需要的状态,而不是整个 Store。

基本使用

jsx
// 只订阅 count 状态
const count = useStore((state) => state.count)

// 只订阅 name 状态
const name = useStore((state) => state.name)

// 订阅多个特定状态
const { count, name } = useStore((state) => ({
  count: state.count,
  name: state.name
}))

Selector 的优势

  1. 减少不必要的重渲染:只有当 Selector 选择的状态发生变化时,组件才会重渲染
  2. 提高代码可读性:明确指出组件依赖的状态
  3. 便于维护:当 Store 结构变化时,只需要更新 Selector

Shallow:浅比较优化

默认情况下,当使用 Selector 返回一个新对象时,Zustand 会进行严格比较(===)。如果每次渲染都返回一个新对象,即使对象的属性值没有变化,也会触发组件重渲染。

浅比较的问题

jsx
// 不好的做法:每次渲染都返回一个新对象
const userInfo = useStore((state) => ({
  name: state.user.name,
  age: state.user.age
}))

在上面的例子中,每次 Store 更新时,都会创建一个新的 userInfo 对象,即使 name 和 age 的值没有变化,也会触发组件重渲染。

使用 Shallow 进行优化

Zustand 提供了 shallow 选项,用于进行浅比较。当启用 shallow 时,Zustand 会比较对象的属性值,而不是对象本身。

jsx
import { useStore } from 'zustand'
import { shallow } from 'zustand/shallow'

// 好的做法:使用 shallow 进行浅比较
const userInfo = useStore(
  (state) => ({
    name: state.user.name,
    age: state.user.age
  }),
  shallow // 启用浅比较
)

现在,只有当 name 或 age 的值发生变化时,才会触发组件重渲染。

数组的浅比较

shallow 也适用于数组:

jsx
import { useStore } from 'zustand'
import { shallow } from 'zustand/shallow'

// 数组的浅比较
const completedTodos = useStore(
  (state) => state.todos.filter((todo) => todo.completed),
  shallow
)

选择器的高级用法

组合选择器

你可以组合多个选择器来创建更复杂的选择逻辑:

javascript
// 创建可复用的选择器
const selectCount = (state) => state.count
const selectName = (state) => state.name

// 在组件中使用
const count = useStore(selectCount)
const name = useStore(selectName)

带参数的选择器

你可以创建带参数的选择器:

javascript
// 创建带参数的选择器
const selectTodoById = (id) => (state) => 
  state.todos.find((todo) => todo.id === id)

// 在组件中使用
function TodoItem({ id }) {
  const todo = useStore(selectTodoById(id))
  // ...
}

性能优化的最佳实践

1. 总是使用 Selector

避免直接使用整个 Store:

jsx
// 不好的做法:订阅整个 Store
const store = useStore()
const count = store.count

// 好的做法:使用 Selector
const count = useStore((state) => state.count)

2. 对于复杂对象使用 Shallow

当 Selector 返回对象或数组时,使用 shallow 进行优化:

jsx
import { shallow } from 'zustand/shallow'

// 好的做法:使用 shallow
const userInfo = useStore(
  (state) => ({
    name: state.user.name,
    age: state.user.age
  }),
  shallow
)

3. 缓存计算结果

对于需要计算的选择器,可以使用记忆化(memoization)来避免重复计算:

javascript
import { createSelector } from 'reselect'

// 创建记忆化的选择器
const selectCompletedTodosCount = createSelector(
  [(state) => state.todos],
  (todos) => todos.filter((todo) => todo.completed).length
)

// 在组件中使用
const completedCount = useStore(selectCompletedTodosCount)

4. 避免在 Selector 中创建新对象

除非使用 shallow,否则避免在 Selector 中创建新对象:

jsx
// 不好的做法:创建新对象且不使用 shallow
const userInfo = useStore((state) => ({
  name: state.user.name,
  age: state.user.age
}))

// 好的做法 1:使用 shallow
const userInfo = useStore(
  (state) => ({
    name: state.user.name,
    age: state.user.age
  }),
  shallow
)

// 好的做法 2:分别选择属性
const name = useStore((state) => state.user.name)
const age = useStore((state) => state.user.age)

性能监控

你可以使用 React DevTools 的 Profiler 功能来监控组件的重渲染情况,确保你的性能优化策略有效。

  1. 打开 React DevTools
  2. 切换到 Profiler 标签
  3. 点击 “Record” 按钮
  4. 与应用交互
  5. 点击 “Stop” 按钮查看分析结果

总结

Selector 和 Shallow 是 Zustand 中强大的性能优化工具:

  • Selector 允许组件精确订阅需要的状态,减少不必要的重渲染
  • Shallow 提供了浅比较功能,优化了对象和数组的订阅
  • 结合使用 Selector 和 Shallow,可以显著提高大型应用的性能

在接下来的章节中,我们将学习 异步逻辑与 API 请求,了解如何在 Zustand 中处理异步操作。