Setup Store 组合式风格 #

什么是 Setup Store? #

Setup Store 是 Pinia 提供的另一种定义 Store 的方式,它使用函数语法,类似于 Vue 3 的 Composition API 中的 setup() 函数。

对比 Options Store #

ts
// Options Store
export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    double: (state) => state.count * 2
  },
  actions: {
    increment() { this.count++ }
  }
})

// Setup Store
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const double = computed(() => count.value * 2)
  function increment() { count.value++ }
  
  return { count, double, increment }
})

基本语法 #

State:使用 ref 或 reactive #

ts
import { defineStore } from 'pinia'
import { ref, reactive } from 'vue'

export const useUserStore = defineStore('user', () => {
  // 使用 ref 定义简单状态
  const name = ref('John')
  const age = ref(25)
  
  // 使用 reactive 定义对象状态
  const profile = reactive({
    email: 'john@example.com',
    address: {
      city: 'Beijing',
      country: 'China'
    }
  })
  
  return { name, age, profile }
})

Getters:使用 computed #

ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  
  // 使用 computed 定义 getter
  const doubleCount = computed(() => count.value * 2)
  const tripleCount = computed(() => count.value * 3)
  
  // 依赖其他 getter
  const quadrupleCount = computed(() => doubleCount.value * 2)
  
  return { count, doubleCount, tripleCount, quadrupleCount }
})

Actions:普通函数 #

ts
import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  
  // Actions 就是普通函数
  function increment() {
    count.value++
  }
  
  function incrementBy(amount: number) {
    count.value += amount
  }
  
  function reset() {
    count.value = 0
  }
  
  // 异步 action
  async function fetchInitialValue() {
    const response = await fetch('/api/initial-value')
    count.value = await response.json()
  }
  
  return { count, increment, incrementBy, reset, fetchInitialValue }
})

完整示例 #

用户管理 Store #

ts
// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

interface User {
  id: number
  name: string
  email: string
  role: 'admin' | 'user'
}

export const useUserStore = defineStore('user', () => {
  // State
  const user = ref<User | null>(null)
  const token = ref<string | null>(localStorage.getItem('token'))
  const loading = ref(false)
  const error = ref<string | null>(null)
  
  // Getters
  const isAuthenticated = computed(() => !!token.value && !!user.value)
  const isAdmin = computed(() => user.value?.role === 'admin')
  const displayName = computed(() => user.value?.name || 'Guest')
  
  // Actions
  async function login(email: string, password: string) {
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password })
      })
      
      const data = await response.json()
      user.value = data.user
      token.value = data.token
      localStorage.setItem('token', data.token)
      return true
    } catch (e) {
      error.value = e instanceof Error ? e.message : 'Login failed'
      return false
    } finally {
      loading.value = false
    }
  }
  
  async function fetchUser() {
    if (!token.value) return
    
    loading.value = true
    try {
      const response = await fetch('/api/user', {
        headers: { Authorization: `Bearer ${token.value}` }
      })
      user.value = await response.json()
    } catch (e) {
      logout()
    } finally {
      loading.value = false
    }
  }
  
  function logout() {
    user.value = null
    token.value = null
    localStorage.removeItem('token')
  }
  
  function clearError() {
    error.value = null
  }
  
  return {
    // State
    user,
    token,
    loading,
    error,
    // Getters
    isAuthenticated,
    isAdmin,
    displayName,
    // Actions
    login,
    fetchUser,
    logout,
    clearError
  }
})

购物车 Store #

ts
// stores/cart.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

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

export const useCartStore = defineStore('cart', () => {
  // State
  const items = ref<CartItem[]>([])
  const discount = ref(0)
  
  // Getters
  const itemCount = computed(() => 
    items.value.reduce((sum, item) => sum + item.quantity, 0)
  )
  
  const subtotal = computed(() => 
    items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
  )
  
  const discountAmount = computed(() => 
    subtotal.value * (discount.value / 100)
  )
  
  const total = computed(() => 
    subtotal.value - discountAmount.value
  )
  
  const isEmpty = computed(() => items.value.length === 0)
  
  // Actions
  function addItem(product: Omit<CartItem, 'id' | 'quantity'>) {
    const existingItem = items.value.find(item => item.productId === product.productId)
    
    if (existingItem) {
      existingItem.quantity++
    } else {
      items.value.push({
        id: Date.now(),
        ...product,
        quantity: 1
      })
    }
  }
  
  function removeItem(itemId: number) {
    const index = items.value.findIndex(item => item.id === itemId)
    if (index !== -1) {
      items.value.splice(index, 1)
    }
  }
  
  function updateQuantity(itemId: number, quantity: number) {
    const item = items.value.find(item => item.id === itemId)
    if (item) {
      if (quantity <= 0) {
        removeItem(itemId)
      } else {
        item.quantity = quantity
      }
    }
  }
  
  function setDiscount(value: number) {
    discount.value = Math.min(100, Math.max(0, value))
  }
  
  function clearCart() {
    items.value = []
    discount.value = 0
  }
  
  return {
    // State
    items,
    discount,
    // Getters
    itemCount,
    subtotal,
    discountAmount,
    total,
    isEmpty,
    // Actions
    addItem,
    removeItem,
    updateQuantity,
    setDiscount,
    clearCart
  }
})

私有属性和方法 #

Setup Store 可以定义不暴露的私有属性:

ts
export const useCounterStore = defineStore('counter', () => {
  // 公共状态
  const count = ref(0)
  
  // 私有变量(不返回)
  const maxCount = 100
  const minCount = 0
  
  // 私有方法(不返回)
  function validate(value: number): boolean {
    return value >= minCount && value <= maxCount
  }
  
  function clamp(value: number): number {
    return Math.min(maxCount, Math.max(minCount, value))
  }
  
  // 公共方法
  function increment() {
    count.value = clamp(count.value + 1)
  }
  
  function decrement() {
    count.value = clamp(count.value - 1)
  }
  
  function setCount(value: number) {
    if (validate(value)) {
      count.value = value
    }
  }
  
  // 只返回公共属性
  return { count, increment, decrement, setCount }
})

使用组合式函数 #

Setup Store 可以使用自定义组合式函数:

定义组合式函数 #

ts
// composables/useAsync.ts
import { ref, Ref } from 'vue'

export function useAsync<T>(asyncFn: () => Promise<T>) {
  const data = ref<T | null>(null) as Ref<T | null>
  const loading = ref(false)
  const error = ref<Error | null>(null)
  
  async function execute() {
    loading.value = true
    error.value = null
    try {
      data.value = await asyncFn()
    } catch (e) {
      error.value = e instanceof Error ? e : new Error(String(e))
    } finally {
      loading.value = false
    }
  }
  
  return { data, loading, error, execute }
}

在 Store 中使用 #

ts
// stores/product.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useAsync } from '@/composables/useAsync'

interface Product {
  id: number
  name: string
  price: number
}

export const useProductStore = defineStore('product', () => {
  const products = ref<Product[]>([])
  
  // 使用组合式函数
  const { loading, error, execute: fetchProducts } = useAsync(async () => {
    const response = await fetch('/api/products')
    products.value = await response.json()
    return products.value
  })
  
  const { loading: creating, execute: createProduct } = useAsync(async (product: Omit<Product, 'id'>) => {
    const response = await fetch('/api/products', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(product)
    })
    const newProduct = await response.json()
    products.value.push(newProduct)
    return newProduct
  })
  
  const productCount = computed(() => products.value.length)
  
  return {
    products,
    loading,
    error,
    creating,
    productCount,
    fetchProducts,
    createProduct
  }
})

重置状态 #

Setup Store 需要手动实现 $reset

ts
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const name = ref('counter')
  
  // 初始状态
  const initialState = {
    count: 0,
    name: 'counter'
  }
  
  function $reset() {
    count.value = initialState.count
    name.value = initialState.name
  }
  
  function increment() {
    count.value++
  }
  
  return { count, name, increment, $reset }
})

使用工厂函数 #

ts
export const useUserStore = defineStore('user', () => {
  // 工厂函数
  function createInitialState() {
    return {
      name: '',
      email: '',
      age: 0
    }
  }
  
  const state = reactive(createInitialState())
  
  function $reset() {
    Object.assign(state, createInitialState())
  }
  
  return { ...toRefs(state), $reset }
})

访问其他 Store #

ts
// stores/order.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useCartStore } from './cart'
import { useUserStore } from './user'

export const useOrderStore = defineStore('order', () => {
  const orders = ref<Order[]>([])
  
  async function createOrder() {
    const cartStore = useCartStore()
    const userStore = useUserStore()
    
    const order = {
      userId: userStore.user?.id,
      items: [...cartStore.items],
      total: cartStore.total,
      createdAt: new Date()
    }
    
    const response = await fetch('/api/orders', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(order)
    })
    
    const newOrder = await response.json()
    orders.value.push(newOrder)
    cartStore.clearCart()
    
    return newOrder
  }
  
  return { orders, createOrder }
})

Setup Store vs Options Store #

特性 Setup Store Options Store
语法 函数式 对象式
灵活性 固定结构
私有属性 支持 不支持
组合式函数 可使用 不可使用
TypeScript 自动推断 需要注解
$reset 需手动实现 自动支持
学习曲线 需了解 Composition API 较低

何时使用 Setup Store #

  • 需要私有属性或方法
  • 需要使用组合式函数
  • 复杂的状态逻辑
  • 需要更好的 TypeScript 推断
  • 熟悉 Composition API

何时使用 Options Store #

  • 刚接触 Pinia
  • 简单的状态管理
  • 喜欢固定结构
  • 团队约定

下一步 #

现在你已经掌握了 Setup Store 的使用,接下来让我们学习组合式函数的更多应用。

最后更新:2026-03-28