Actions 方法 #

什么是 Actions? #

Actions 是 Store 中定义业务逻辑的地方,类似于组件中的 methods。与 Vuex 不同,Pinia 的 Actions 可以直接修改状态,无需通过 mutations。

定义 Actions #

基本语法 #

ts
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  actions: {
    increment() {
      this.count++
    },
    
    incrementBy(amount: number) {
      this.count += amount
    },
    
    reset() {
      this.count = 0
    }
  }
})

访问 State 和 Getters #

ts
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    doubleCount(): number {
      return this.count * 2
    }
  },
  actions: {
    // 访问 state
    increment() {
      this.count++
    },
    
    // 访问 getter
    incrementDouble() {
      this.count = this.doubleCount
    },
    
    // 同时访问 state 和 getter
    setToDouble() {
      this.count = this.doubleCount
    }
  }
})

使用 Actions #

在组件中调用 #

vue
<template>
  <div>
    <p>Count: {{ counter.count }}</p>
    <button @click="counter.increment">+1</button>
    <button @click="counter.incrementBy(5)">+5</button>
    <button @click="counter.reset">Reset</button>
  </div>
</template>

<script setup>
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()
</script>

解构使用 #

Actions 可以直接解构,无需 storeToRefs

vue
<script setup>
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()

// 直接解构 actions
const { increment, incrementBy, reset } = counter

// 或者使用 storeToRefs 解构 state,单独获取 actions
const { count } = storeToRefs(counter)
const { increment } = counter
</script>

异步 Actions #

Actions 天然支持异步操作:

基本异步操作 #

ts
export const useProductStore = defineStore('product', {
  state: () => ({
    products: [],
    loading: false,
    error: null as string | null
  }),
  actions: {
    async fetchProducts() {
      this.loading = true
      this.error = null
      
      try {
        const response = await fetch('/api/products')
        this.products = await response.json()
      } catch (error) {
        this.error = error.message
      } finally {
        this.loading = false
      }
    }
  }
})

使用 async/await #

ts
export const useAuthStore = defineStore('auth', {
  state: () => ({
    user: null as User | null,
    token: null as string | null
  }),
  actions: {
    async login(credentials: { email: string; password: string }) {
      try {
        const response = await api.login(credentials)
        this.user = response.user
        this.token = response.token
        return true
      } catch (error) {
        console.error('Login failed:', error)
        return false
      }
    },
    
    async logout() {
      await api.logout()
      this.user = null
      this.token = null
    }
  }
})

并行请求 #

ts
export const useDashboardStore = defineStore('dashboard', {
  state: () => ({
    users: [],
    products: [],
    orders: []
  }),
  actions: {
    async fetchAll() {
      const [users, products, orders] = await Promise.all([
        api.getUsers(),
        api.getProducts(),
        api.getOrders()
      ])
      
      this.users = users
      this.products = products
      this.orders = orders
    }
  }
})

调用其他 Actions #

同一 Store 内 #

ts
export const useCartStore = defineStore('cart', {
  state: () => ({
    items: [] as CartItem[],
    total: 0
  }),
  actions: {
    addItem(item: CartItem) {
      this.items.push(item)
      this.calculateTotal()
    },
    
    removeItem(itemId: number) {
      this.items = this.items.filter(item => item.id !== itemId)
      this.calculateTotal()
    },
    
    calculateTotal() {
      this.total = this.items.reduce((sum, item) => sum + item.price, 0)
    },
    
    clearCart() {
      this.items = []
      this.total = 0
    }
  }
})

跨 Store 调用 #

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

export const useOrderStore = defineStore('order', {
  state: () => ({
    orders: [] as Order[]
  }),
  actions: {
    async 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 api.createOrder(order)
      this.orders.push(response)
      
      // 清空购物车
      cartStore.clearCart()
      
      return response
    }
  }
})

Setup Store 中的 Actions #

在 Setup Store 中,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
  }
  
  async function fetchInitialValue() {
    const response = await fetch('/api/initial-value')
    count.value = await response.json()
  }
  
  return {
    count,
    increment,
    incrementBy,
    fetchInitialValue
  }
})

订阅 Actions #

$onAction #

监听 action 的调用:

ts
const counterStore = useCounterStore()

counterStore.$onAction(({ 
  name,      // action 名称
  args,      // action 参数
  after,     // action 成功后的钩子
  onError    // action 出错时的钩子
}) => {
  console.log(`Action ${name} called with args:`, args)
  
  after((result) => {
    console.log(`Action ${name} finished with result:`, result)
  })
  
  onError((error) => {
    console.error(`Action ${name} failed with error:`, error)
  })
})

实际应用示例 #

ts
// 日志记录
const userStore = useUserStore()

userStore.$onAction(({ name, args, after, onError }) => {
  const startTime = Date.now()
  console.log(`[Action] ${name} started`)
  
  after(() => {
    const duration = Date.now() - startTime
    console.log(`[Action] ${name} completed in ${duration}ms`)
  })
  
  onError((error) => {
    console.error(`[Action] ${name} failed:`, error)
  })
})

移除订阅 #

ts
const unsubscribe = userStore.$onAction(() => {
  // ...
})

// 移除订阅
unsubscribe()

错误处理 #

Try-Catch #

ts
export const useProductStore = defineStore('product', {
  state: () => ({
    products: [],
    loading: false,
    error: null as string | null
  }),
  actions: {
    async fetchProducts() {
      this.loading = true
      this.error = null
      
      try {
        const response = await fetch('/api/products')
        if (!response.ok) {
          throw new Error('Failed to fetch products')
        }
        this.products = await response.json()
      } catch (error) {
        this.error = error instanceof Error ? error.message : 'Unknown error'
        throw error  // 可以选择重新抛出
      } finally {
        this.loading = false
      }
    }
  }
})

全局错误处理 #

ts
// main.ts
const pinia = createPinia()

pinia.use(({ store }) => {
  store.$onAction(({ name, onError }) => {
    onError((error) => {
      console.error(`Error in ${store.$id}.${name}:`, error)
      // 可以发送到错误追踪服务
      // trackError(error)
    })
  })
})

实际示例 #

用户认证 Store #

ts
// stores/auth.ts
import { defineStore } from 'pinia'
import { api } from '@/api'

interface User {
  id: number
  email: string
  name: string
  role: string
}

export const useAuthStore = defineStore('auth', {
  state: () => ({
    user: null as User | null,
    token: localStorage.getItem('token'),
    loading: false,
    error: null as string | null
  }),
  
  getters: {
    isAuthenticated: (state) => !!state.token && !!state.user,
    isAdmin: (state) => state.user?.role === 'admin'
  },
  
  actions: {
    async login(email: string, password: string) {
      this.loading = true
      this.error = null
      
      try {
        const response = await api.login({ email, password })
        this.token = response.token
        this.user = response.user
        localStorage.setItem('token', response.token)
        return true
      } catch (error) {
        this.error = error instanceof Error ? error.message : 'Login failed'
        return false
      } finally {
        this.loading = false
      }
    },
    
    async register(data: { email: string; password: string; name: string }) {
      this.loading = true
      this.error = null
      
      try {
        const response = await api.register(data)
        this.token = response.token
        this.user = response.user
        localStorage.setItem('token', response.token)
        return true
      } catch (error) {
        this.error = error instanceof Error ? error.message : 'Registration failed'
        return false
      } finally {
        this.loading = false
      }
    },
    
    async fetchUser() {
      if (!this.token) return
      
      try {
        const user = await api.getCurrentUser()
        this.user = user
      } catch (error) {
        this.logout()
      }
    },
    
    logout() {
      this.user = null
      this.token = null
      localStorage.removeItem('token')
    },
    
    clearError() {
      this.error = null
    }
  }
})

数据获取 Store #

ts
// stores/product.ts
import { defineStore } from 'pinia'
import { api } from '@/api'

interface Product {
  id: number
  name: string
  price: number
  description: string
  category: string
  stock: number
}

export const useProductStore = defineStore('product', {
  state: () => ({
    products: [] as Product[],
    currentProduct: null as Product | null,
    loading: false,
    error: null as string | null,
    filters: {
      category: null as string | null,
      minPrice: null as number | null,
      maxPrice: null as number | null
    }
  }),
  
  getters: {
    filteredProducts: (state) => {
      let result = state.products
      
      if (state.filters.category) {
        result = result.filter(p => p.category === state.filters.category)
      }
      
      if (state.filters.minPrice !== null) {
        result = result.filter(p => p.price >= state.filters.minPrice!)
      }
      
      if (state.filters.maxPrice !== null) {
        result = result.filter(p => p.price <= state.filters.maxPrice!)
      }
      
      return result
    }
  },
  
  actions: {
    async fetchProducts() {
      this.loading = true
      this.error = null
      
      try {
        this.products = await api.getProducts()
      } catch (error) {
        this.error = error instanceof Error ? error.message : 'Failed to fetch products'
      } finally {
        this.loading = false
      }
    },
    
    async fetchProduct(id: number) {
      this.loading = true
      this.error = null
      
      try {
        this.currentProduct = await api.getProduct(id)
      } catch (error) {
        this.error = error instanceof Error ? error.message : 'Failed to fetch product'
      } finally {
        this.loading = false
      }
    },
    
    async createProduct(product: Omit<Product, 'id'>) {
      this.loading = true
      this.error = null
      
      try {
        const newProduct = await api.createProduct(product)
        this.products.push(newProduct)
        return newProduct
      } catch (error) {
        this.error = error instanceof Error ? error.message : 'Failed to create product'
        return null
      } finally {
        this.loading = false
      }
    },
    
    async updateProduct(id: number, updates: Partial<Product>) {
      this.loading = true
      this.error = null
      
      try {
        const updated = await api.updateProduct(id, updates)
        const index = this.products.findIndex(p => p.id === id)
        if (index !== -1) {
          this.products[index] = updated
        }
        if (this.currentProduct?.id === id) {
          this.currentProduct = updated
        }
        return updated
      } catch (error) {
        this.error = error instanceof Error ? error.message : 'Failed to update product'
        return null
      } finally {
        this.loading = false
      }
    },
    
    async deleteProduct(id: number) {
      this.loading = true
      this.error = null
      
      try {
        await api.deleteProduct(id)
        this.products = this.products.filter(p => p.id !== id)
        if (this.currentProduct?.id === id) {
          this.currentProduct = null
        }
        return true
      } catch (error) {
        this.error = error instanceof Error ? error.message : 'Failed to delete product'
        return false
      } finally {
        this.loading = false
      }
    },
    
    setFilters(filters: Partial<typeof this.filters>) {
      this.filters = { ...this.filters, ...filters }
    },
    
    clearFilters() {
      this.filters = {
        category: null,
        minPrice: null,
        maxPrice: null
      }
    }
  }
})

Actions 最佳实践 #

1. 单一职责 #

ts
// 推荐:每个 action 做一件事
actions: {
  async fetchProducts() { /* ... */ },
  async createProduct(product) { /* ... */ },
  async updateProduct(id, updates) { /* ... */ },
  async deleteProduct(id) { /* ... */ }
}

// 不推荐:一个 action 做多件事
actions: {
  async handleProducts(action, data) { /* ... */ }
}

2. 错误处理 #

ts
// 推荐:完善的错误处理
actions: {
  async fetchData() {
    this.loading = true
    this.error = null
    try {
      const data = await api.getData()
      this.data = data
    } catch (error) {
      this.error = error.message
      throw error
    } finally {
      this.loading = false
    }
  }
}

3. 返回值 #

ts
// 推荐:返回有意义的结果
actions: {
  async login(credentials) {
    try {
      const response = await api.login(credentials)
      this.user = response.user
      return { success: true, user: response.user }
    } catch (error) {
      return { success: false, error: error.message }
    }
  }
}

下一步 #

现在你已经掌握了 Actions 的使用,接下来让我们学习组合式 API 风格的 Store。

最后更新:2026-03-28