掌握中间件与持久化

学习如何将状态保存到 LocalStorage、IndexedDB 以及创建自定义中间件。

基础持久化

`persist` 中间件允许你将状态存储在存储器中(例如 localStorage、AsyncStorage、IndexedDB 等),这样当用户刷新页面时,状态得以保留。

import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

export const useBearStore = create(
  persist(
    (set, get) => ({
      bears: 0,
      addABear: () => set({ bears: get().bears + 1 }),
    }),
    {
      name: 'food-storage', // name of the item in the storage (must be unique)
      storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
    },
  ),
)

处理 Hydration 错误

在 Next.js 中,你可能会遇到 hydration 错误,因为服务器渲染初始状态(0 只熊),但客户端使用持久化状态(5 只熊)进行 hydration。

解决方案:自定义 Hook

import { useState, useEffect } from 'react'

const useStore = <T, F>(
  store: (callback: (state: T) => unknown) => unknown,
  callback: (state: T) => F,
) => {
  const result = store(callback) as F
  const [data, setData] = useState<F>()

  useEffect(() => {
    setData(result)
  }, [result])

  return data
}

Custom Storage Engines

You can use any storage engine that implements `getItem`, `setItem`, and `removeItem`.

Example: IndexedDB (using idb-keyval)

import { create } from 'zustand'
import { persist, createJSONStorage, StateStorage } from 'zustand/middleware'
import { get, set, del } from 'idb-keyval' // npm install idb-keyval

const storage: StateStorage = {
  getItem: async (name: string): Promise<string | null> => {
    console.log(name, 'has been retrieved')
    return (await get(name)) || null
  },
  setItem: async (name: string, value: string): Promise<void> => {
    console.log(name, 'with value', value, 'has been saved')
    await set(name, value)
  },
  removeItem: async (name: string): Promise<void> => {
    console.log(name, 'has been deleted')
    await del(name)
  },
}

export const useBoundStore = create(
  persist(
    (set, get) => ({
      bears: 0,
      addABear: () => set({ bears: get().bears + 1 }),
    }),
    {
      name: 'food-storage', // unique name
      storage: createJSONStorage(() => storage),
    },
  ),
)