06-状态管理Zustand

2 阅读2分钟

🧠 Taro 从零到一(六):状态管理 — Zustand 轻量之道

系列导读:小程序也需要全局状态管理。Zustand 是 React 生态中最轻量的状态管理库, 配合 Taro 使用体验极佳,零模板代码。


📊 为什么选 Zustand?

方案代码量学习成本Taro 兼容适合规模
useState极少单组件
Redux Toolkit较多⭐⭐⭐大型项目
MobX中等⭐⭐⭐中大型
Zustand极少中小型推荐

🛠 1. 安装与基础用法

npm install zustand
// src/store/useCounterStore.ts
import { create } from 'zustand'

interface CounterState {
  count: number
  increment: () => void
  decrement: () => void
  reset: () => void
}

const useCounterStore = create<CounterState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}))

export default useCounterStore
// 在组件中使用
import useCounterStore from '@/store/useCounterStore'

function CounterPage() {
  const { count, increment, decrement, reset } = useCounterStore()

  return (
    <View>
      <Text className="count">{count}</Text>
      <Button onClick={increment}>+1</Button>
      <Button onClick={decrement}>-1</Button>
      <Button onClick={reset}>重置</Button>
    </View>
  )
}

🛒 2. 实战:购物车 Store

// src/store/useCartStore.ts
import { create } from 'zustand'

interface CartItem {
  id: string
  name: string
  price: number
  image: string
  quantity: number
}

interface CartState {
  items: CartItem[]
  addItem: (product: Omit<CartItem, 'quantity'>) => void
  removeItem: (id: string) => void
  updateQuantity: (id: string, quantity: number) => void
  clearCart: () => void
  totalCount: () => number
  totalPrice: () => number
}

const useCartStore = create<CartState>((set, get) => ({
  items: [],

  addItem: (product) => set((state) => {
    const existing = state.items.find(item => item.id === product.id)
    if (existing) {
      return {
        items: state.items.map(item =>
          item.id === product.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        ),
      }
    }
    return { items: [...state.items, { ...product, quantity: 1 }] }
  }),

  removeItem: (id) => set((state) => ({
    items: state.items.filter(item => item.id !== id),
  })),

  updateQuantity: (id, quantity) => set((state) => ({
    items: quantity <= 0
      ? state.items.filter(item => item.id !== id)
      : state.items.map(item =>
          item.id === id ? { ...item, quantity } : item
        ),
  })),

  clearCart: () => set({ items: [] }),

  totalCount: () => get().items.reduce((sum, item) => sum + item.quantity, 0),

  totalPrice: () => get().items.reduce((sum, item) => sum + item.price * item.quantity, 0),
}))

export default useCartStore
// 商品页 — 添加到购物车
function ProductDetail({ product }) {
  const addItem = useCartStore(state => state.addItem)

  return (
    <Button onClick={() => {
      addItem(product)
      Taro.showToast({ title: '已加入购物车', icon: 'success' })
    }}>
      加入购物车
    </Button>
  )
}

// 购物车页
function CartPage() {
  const { items, updateQuantity, removeItem, totalPrice, clearCart } = useCartStore()

  if (items.length === 0) return <Empty description="购物车空空如也" />

  return (
    <View className="cart-page">
      {items.map(item => (
        <View key={item.id} className="cart-item">
          <Image src={item.image} className="item-image" />
          <View className="item-info">
            <Text className="item-name">{item.name}</Text>
            <Text className="item-price">¥{item.price}</Text>
          </View>
          <View className="quantity-control">
            <Button onClick={() => updateQuantity(item.id, item.quantity - 1)}>-</Button>
            <Text>{item.quantity}</Text>
            <Button onClick={() => updateQuantity(item.id, item.quantity + 1)}>+</Button>
          </View>
        </View>
      ))}

      <View className="cart-footer">
        <Text className="total">合计:¥{totalPrice().toFixed(2)}</Text>
        <Button type="primary" onClick={() => Taro.navigateTo({ url: '/pages/checkout/index' })}>
          去结算
        </Button>
      </View>
    </View>
  )
}

// TabBar 角标 — 精确选择只需要的数据
function TabBadge() {
  const count = useCartStore(state => state.totalCount())
  // 只有 totalCount 变化才重渲染,其他不受影响
  return count > 0 ? <Text className="badge">{count}</Text> : null
}

💾 3. 持久化存储

// 配合 Taro Storage 持久化
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
import Taro from '@tarojs/taro'

// Taro Storage 适配器
const taroStorage = createJSONStorage(() => ({
  getItem: (name: string) => Taro.getStorageSync(name),
  setItem: (name: string, value: string) => Taro.setStorageSync(name, value),
  removeItem: (name: string) => Taro.removeStorageSync(name),
}))

const useAuthStore = create(
  persist<AuthState>(
    (set) => ({
      token: '',
      user: null,
      setAuth: (token, user) => set({ token, user }),
      logout: () => set({ token: '', user: null }),
    }),
    {
      name: 'auth-storage',
      storage: taroStorage,
    }
  )
)

✅ 本篇小结 Checklist

  • 安装并使用 Zustand 创建 Store
  • 实现购物车增删改查逻辑
  • 掌握精确选择(selector)优化渲染
  • 配合 Taro Storage 实现持久化

下一篇预告:《网络请求与数据处理》


本文是「Taro 从零到一」系列第 6 篇,共 10 篇。


📝 作者:NIHoa | 系列:Taro从零到一系列 | 更新日期:2024-06-06