5月28日 02:37
如何创建和使用 Zustand store?
核心答案
Zustand 通过 create 函数创建 store,返回一个可直接在组件中使用的 Hook。与 Redux 不同,它不需要 Provider 包裹,store 本身就是 Hook:
javascriptimport { create } from 'zustand' const useStore = create((set, get) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), reset: () => set({ count: 0 }), }))
组件中使用时,推荐通过选择器订阅,避免不必要的重渲染:
javascriptconst count = useStore((state) => state.count) // 只订阅 count const increment = useStore((state) => state.increment) // 只订阅 increment
set 与 get 的用法
set 用于更新状态,支持对象和函数两种形式。Zustand 自动浅合并第一层属性,所以不需要手动展开 ...state:
javascriptconst useStore = create((set) => ({ user: { name: 'Tom', age: 20 }, // 对象形式:直接替换第一层属性 setName: (name) => set({ user: { name, age: 20 } }), // 注意:第二层需手动处理 // 函数形式:基于旧状态计算 incrementAge: () => set((state) => ({ user: { ...state.user, age: state.user.age + 1 } })), }))
get 用于在 action 中读取当前状态,不触发订阅:
javascriptconst useStore = create((set, get) => ({ items: [], addItem: (item) => set({ items: [...get().items, item] }), getCount: () => get().items.length, // 不触发重渲染 }))
选择性订阅与性能优化
直接解构整个 store 会导致任何状态变化都触发重渲染,应避免:
javascript// 不推荐:任何状态变化都触发重渲染 const { count, name } = useStore() // 推荐:按需订阅 const count = useStore((s) => s.count) const name = useStore((s) => s.name)
对于复杂对象,使用 shallow 比较避免引用变化导致的重渲染:
javascriptimport { shallow } from 'zustand/shallow' const { name, age } = useStore( (s) => ({ name: s.user.name, age: s.user.age }), shallow )
Store 拆分(Slice 模式)
大型应用中,将不同领域的状态拆成独立 slice,再合并到一个 store:
javascript// slices/cartSlice.js export const createCartSlice = (set) => ({ items: [], addItem: (item) => set((s) => ({ items: [...s.items, item] })), clearCart: () => set({ items: [] }), }) // slices/userSlice.js export const createUserSlice = (set) => ({ user: null, setUser: (user) => set({ user }), }) // store.js import { create } from 'zustand' import { createCartSlice } from './slices/cartSlice' import { createUserSlice } from './slices/userSlice' const useStore = create((...a) => ({ ...createCartSlice(...a), ...createUserSlice(...a), }))
异步操作
Zustand 的 action 可以直接是 async 函数,不需要额外的中间件:
javascriptconst useStore = create((set) => ({ data: null, loading: false, error: null, fetchData: async (id) => { set({ loading: true, error: null }) try { const res = await fetch(`/api/data/${id}`) const data = await res.json() set({ data, loading: false }) } catch (error) { set({ error: error.message, loading: false }) } }, }))
常用中间件
persist — 持久化到 localStorage
javascriptimport { create } from 'zustand' import { persist } from 'zustand/middleware' const useStore = create( persist( (set) => ({ theme: 'light', setTheme: (theme) => set({ theme }), }), { name: 'theme-storage' } // localStorage key ) )
immer — 不可变更新的简化写法
javascriptimport { create } from 'zustand' import { immer } from 'zustand/middleware/immer' const useStore = create( immer((set) => ({ user: { name: 'Tom', address: { city: 'Beijing' } }, setCity: (city) => set((state) => { state.user.address.city = city }), // 无需手动展开,直接修改 draft })) )
devtools — Redux DevTools 调试支持
javascriptimport { create } from 'zustand' import { devtools } from 'zustand/middleware' const useStore = create( devtools((set) => ({ count: 0, increment: () => set((s) => ({ count: s.count + 1 })), }), { name: 'CounterStore' }) )
中间件可以组合使用,顺序从外到内:devtools(persist(immer(...)))。
create 与 createStore 的区别
create | createStore | |
|---|---|---|
| 返回值 | React Hook | Store 对象 |
| 使用场景 | React 组件内 | React 外(测试、服务端、非React环境) |
| 订阅方式 | useStore(s => s.xxx) | store.subscribe() / store.getState() |
javascriptimport { createStore } from 'zustand' const store = createStore((set) => ({ count: 0, increment: () => set((s) => ({ count: s.count + 1 })), })) // React 外部使用 store.getState().count // 读取 store.setState({ count: 10 }) // 更新 store.subscribe((state) => { // 监听 console.log('state changed', state) })
追问:Zustand 与 Redux 的核心区别是什么?
- 无需 Provider:Zustand 不需要
<Provider>包裹组件树,直接导入 Hook 使用 - 订阅粒度:Zustand 通过选择器精确订阅,Redux 用
useSelector实现类似效果但机制不同 - 样板代码:Zustand 无 action type、reducer、dispatch,一个函数搞定
- Bundle 体积:Zustand ~1KB vs Redux Toolkit ~11KB
- 中间件生态:Redux 有更成熟的中间件链,Zustand 的中间件更轻量但够用