一个轻量级状态管理库

Store

一个包含应用状态的对象,每当状态改变时,订阅这个 store 的组件会自动重新渲染
刷新页面,状态会消失

创建 Store

使用 create 函数创建 store:

import { create } from 'zustand'
 
const useStore = create((set, get, store) => ({
  // 第一层状态:
  bears: 0, // 状态数据
  
  // 下面的函数也叫 Action
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })), // 根据先前的 state 设置新 state
  removeAllBears: () => set({ bears: 0 }), // 直接设置新 state
  updateBears: (newBears) => set({ bears: newBears }), // 根据入参设置新 state
}))

最好将状态数据和它对应的 Action 操作放在一个 store 内部

create 函数

传入一个函数
函数的参数是:

  • set:用来更新状态,会与 store 中的现有 state 状态的第一层进行浅合并,state 必须要当作不可变数据进行处理,
  • get:调用 get() 可以得到 state 里的所有内容,用于在 set 之外拿到 state
  • store:提供底层的 API 调用功能,高级用法

函数返回值:

  • 一个带有 API 工具的 React Hook,包括 setStategetStategetInitialStatesubscribe

嵌套 state 对象:

zustand 只会将 state 的第一层进行浅合并,如果要 set 深层对象:

1.使用扩展运算符

import { create } from 'zustand'
 
const useStore = create((set) => ({
  nested: {
    count: 0
  },
  inc: () =>
    set((state) => ({
      nested: {
        ...state.nested,
        count: state.nested.count + 1
      },
    })),
}))

2.使用 Immer 中间件

先安装 immer 库

import { create } from 'zustand'
 
const useStore = create(
  immer((set) => ({
    nested: {
      count: 0
    },
    inc: () =>
      set((state) => { // 注意:不用返回一个对象了
        state.nested.count += 1 // 以看似“可变”的方式来编写状态更新逻辑
      }),
  }))
)

异步操作

可以直接在状态对象中添加一个异步函数,这个函数可以使用 set 函数来更新状态:

import { create } from 'zustand'
 
const useStore = create((set, get, store) => ({
  fetchData: async () => {
    const response = await fetch('/api/xxx')
    const items = await response.json()
    set({ items })
  }
}))

重置 store 内的所有状态为初始值

const useSomeStore = create((set, get, store) => ({
  // your code here
  reset: () => {
    set(store.getInitialState())
  },
}))

组件中使用

读取

通过 useStore 读取状态:

function BearCounter() {
  const { bears } = useStore() // 读取对应的 state
  
  return <h1>{bears} bears around here...</h1> // 使用 state
}

性能优化

1.使用 selector 选择性读取单个值

如果一个组件直接使用 useStore() 读取了所有 state 值,但是只用到其中的部分值,这时候没有用到的值发生变化时组件也会重渲染

function BearCounter() {
  const bears = useStore((state) => state.bears) // 使用 selector 读取对应的 state
  
  return <h1>{bears} bears around here...</h1> // 使用 state
}

或者封装成 selector hook:

import { create } from 'zustand'
 
const useStore = create((set, get, api) => ({
  bears: 0,
}))
 
export const useBears = () => useStore((state) => state.bears) // selector hook

组件中使用:

function BearCounter() {
  const bears = useBears() // 使用 selector 读取对应的 state
  
  return <h1>{bears} bears around here...</h1> // 使用 state
}

2.使用 useShallow 防止重复渲染

const useStore = create((set) => ({
  name: 'Alice',
  age: 25,
}))

如果确实要同时读取多个状态值:

// 组件中
const { name, age } = useStore(state => ({ name: state.name, age: state.age }))

这里的关键问题是:

  • state => ({ name: state.name, age: state.age }) 这个选择器函数在每次组件渲染时都会执行,并返回一个全新的对象;
  • 即使 state.namestate.age 的值没有变化,但这个新对象的引用地址和上一次是不同的;
  • 这会导致组件在任何 Store 状态变化时都重新渲染。

使用 useShallow
原理:新旧对象的所有顶层属性都严格相等,那么 shallow 认为这两个对象是相等的,组件不会重新渲染

// 组件中
import { useShallow } from 'zustand/react/shallow'
 
const { name, age } = useStore(useShallow(state => ({ name: state.name, age: state.age })))

subscribe 监听状态变更

store 中的数据变了执行对应函数

// 组件中
useEffect(() => {
  // 1.订阅整个 store 的变化
  const unsubscribe = useStore.subscribe(
    (state, previousState) => {
      console.log('状态变化了:', previousState, '→', state)
    },
  )
 
  // 2.订阅 bears 的变化
  const unsubscribe = useStore.subscribe(
    (state) => state.bears,
    
    // bears 变化后触发
    (newV, oldV) => {
      console.log(`bears 从 ${oldV} 变为 ${newV}`)
    }
  )
 
  return unsubscribe
}, [])

状态与 URL 的 hash 同步

把某些状态存在 url 的 hash 中,可以实现:

  • 刷新页面不丢失
  • 分享出去的链接包含当前页面的完整状态
  • 可以收藏特定状态的页面作为书签

技术步骤:

  1. 创建一个 store,存放某个 state 状态
  2. 写同步逻辑:当 state 变化:
    • 把 state 写到 location.hash
    • 当用户前进/后退/刷新 → 监听 window.onhashchange 事件,把 hash 里的 state 读回来

持久化

使用 persist 中间件

参考