服务端阅读 05月28日 02:37
如何创建和使用 Zustand store?
核心答案Zustand 通过 create 函数创建 store,返回一个可直接在组件中使用的 Hook。与 Redux 不同,它不需要 Provider 包裹,store 本身就是 Hook:import { create } from 'zustand'const useStore = create((set, get) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), reset: () => set({ count: 0 }),}))组件中使用时,推荐通过选择器订阅,避免不必要的重渲染:const count = useStore((state) => state.count) // 只订阅 countconst increment = useStore((state) => state.increment) // 只订阅 incrementset 与 get 的用法set 用于更新状态,支持对象和函数两种形式。Zustand 自动浅合并第一层属性,所以不需要手动展开 ...state:const 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 中读取当前状态,不触发订阅:const useStore = create((set, get) => ({ items: [], addItem: (item) => set({ items: [...get().items, item] }), getCount: () => get().items.length, // 不触发重渲染}))选择性订阅与性能优化直接解构整个 store 会导致任何状态变化都触发重渲染,应避免:// 不推荐:任何状态变化都触发重渲染const { count, name } = useStore()// 推荐:按需订阅const count = useStore((s) => s.count)const name = useStore((s) => s.name)对于复杂对象,使用 shallow 比较避免引用变化导致的重渲染:import { shallow } from 'zustand/shallow'const { name, age } = useStore( (s) => ({ name: s.user.name, age: s.user.age }), shallow)Store 拆分(Slice 模式)大型应用中,将不同领域的状态拆成独立 slice,再合并到一个 store:// slices/cartSlice.jsexport const createCartSlice = (set) => ({ items: [], addItem: (item) => set((s) => ({ items: [...s.items, item] })), clearCart: () => set({ items: [] }),})// slices/userSlice.jsexport const createUserSlice = (set) => ({ user: null, setUser: (user) => set({ user }),})// store.jsimport { create } from 'zustand'import { createCartSlice } from './slices/cartSlice'import { createUserSlice } from './slices/userSlice'const useStore = create((...a) => ({ ...createCartSlice(...a), ...createUserSlice(...a),}))异步操作Zustand 的 action 可以直接是 async 函数,不需要额外的中间件:const 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 — 持久化到 localStorageimport { 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 — 不可变更新的简化写法import { 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 调试支持import { 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() |import { 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 的中间件更轻量但够用