Skip to content
On this page

Pinia快速入门

Pinia是Vue.js的现代化状态管理库,由Vue核心团队成员开发。它提供了一个更简单、更直观的方式来管理应用状态,相比Vuex,Pinia具有更好的TypeScript支持和更简洁的API。

安装Pinia

使用npm安装

bash
npm install pinia

使用yarn安装

bash
yarn add pinia

使用pnpm安装

bash
pnpm add pinia

在Vue应用中集成Pinia

基础设置

javascript
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')

TypeScript支持

typescript
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import type { App } from 'vue'
import App from './App.vue'

const app: App = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.mount('#app')

创建第一个Store

定义Store

typescript
// stores/counter.ts
import { defineStore } from 'pinia'

// 定义一个名为 'counter' 的store
export const useCounterStore = defineStore('counter', {
  // 状态
  state: () => {
    return {
      count: 0,
      name: 'Eduardo',
      isAdmin: true
    }
  },
  
  // 计算属性
  getters: {
    doubleCount: (state) => state.count * 2,
    
    // 访问其他getter
    doubleCountPlusOne(): number {
      return this.doubleCount + 1
    }
  },
  
  // 动作
  actions: {
    increment() {
      this.count++
    },
    
    decrement() {
      this.count--
    },
    
    async incrementAsync() {
      await new Promise(resolve => setTimeout(resolve, 1000))
      this.count++
    }
  }
})

在组件中使用Store

vue
<!-- components/Counter.vue -->
<template>
  <div>
    <p>Count: {{ counterStore.count }}</p>
    <p>Double Count: {{ counterStore.doubleCount }}</p>
    <p>Name: {{ counterStore.name }}</p>
    <button @click="counterStore.increment()">Increment</button>
    <button @click="counterStore.decrement()">Decrement</button>
    <button @click="incrementAsync()">Increment Async</button>
  </div>
</template>

<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'

const counterStore = useCounterStore()

const incrementAsync = async () => {
  await counterStore.incrementAsync()
}
</script>

Store结构详解

State(状态)

State是存储数据的地方,相当于组件中的data。

typescript
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: '',
    age: 0,
    email: '',
    isLoggedIn: false
  })
})

Getters(计算属性)

Getters是store的计算属性,用于派生数据。

typescript
export const useUserStore = defineStore('user', {
  state: () => ({
    name: 'Alice',
    age: 25,
    hobbies: ['reading', 'swimming', 'coding']
  }),
  
  getters: {
    // 基本getter
    getFullName: (state) => `${state.name}`,
    
    // 使用this访问其他getter
    getProfileInfo(): string {
      return `${this.getFullName}, ${this.age} years old`
    },
    
    // 接收其他参数的getter
    getHobbyCount: (state) => state.hobbies.length,
    
    // 带参数的getter
    getHobbiesByLength: (state) => (minLength: number) => {
      return state.hobbies.filter(hobby => hobby.length >= minLength)
    }
  }
})

Actions(动作)

Actions是store中的方法,用于改变state或执行异步操作。

typescript
export const useUserStore = defineStore('user', {
  state: () => ({
    user: null as User | null,
    loading: false,
    error: null as string | null
  }),
  
  actions: {
    // 同步action
    setUser(user: User) {
      this.user = user
      this.error = null
    },
    
    // 异步action
    async fetchUser(userId: string) {
      this.loading = true
      try {
        const response = await fetch(`/api/users/${userId}`)
        const userData = await response.json()
        this.user = userData
      } catch (error) {
        this.error = (error as Error).message
      } finally {
        this.loading = false
      }
    },
    
    // 清除用户数据
    clearUser() {
      this.user = null
      this.error = null
    }
  }
})

在组件中使用Store的多种方式

方式1:直接使用

vue
<template>
  <div>
    <p>{{ counterStore.count }}</p>
    <button @click="counterStore.increment()">+1</button>
  </div>
</template>

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

const counterStore = useCounterStore()
</script>

方式2:使用解构

vue
<template>
  <div>
    <p>{{ count }}</p>
    <p>{{ doubleCount }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

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

const counterStore = useCounterStore()

// 使用storeToRefs保持响应性
const { count, doubleCount } = storeToRefs(counterStore)
const { increment } = counterStore
</script>

方式3:在选项式API中使用

vue
<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

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

export default {
  computed: {
    // 使用计算属性访问store数据
    count() {
      return useCounterStore().count
    },
    doubleCount() {
      return useCounterStore().doubleCount
    }
  },
  
  methods: {
    increment() {
      useCounterStore().increment()
    }
  }
}
</script>

实际应用示例

购物车Store

typescript
// stores/cart.ts
import { defineStore } from 'pinia'

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

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: [] as CartItem[],
    discount: 0
  }),
  
  getters: {
    subtotal(): number {
      return this.items.reduce(
        (total, item) => total + item.price * item.quantity, 
        0
      )
    },
    
    total(): number {
      return this.subtotal - this.discount
    },
    
    itemCount(): number {
      return this.items.reduce(
        (count, item) => count + item.quantity, 
        0
      )
    }
  },
  
  actions: {
    addItem(item: Omit<CartItem, 'quantity'>) {
      const existingItem = this.items.find(i => i.id === item.id)
      if (existingItem) {
        existingItem.quantity += 1
      } else {
        this.items.push({ ...item, quantity: 1 })
      }
    },
    
    removeItem(itemId: string) {
      this.items = this.items.filter(item => item.id !== itemId)
    },
    
    updateQuantity(itemId: string, quantity: number) {
      if (quantity <= 0) {
        this.removeItem(itemId)
        return
      }
      
      const item = this.items.find(i => i.id === itemId)
      if (item) {
        item.quantity = quantity
      }
    },
    
    clearCart() {
      this.items = []
      this.discount = 0
    },
    
    setDiscount(amount: number) {
      this.discount = Math.min(amount, this.subtotal)
    }
  }
})

在组件中使用购物车

vue
<template>
  <div class="cart">
    <h2>购物车 ({{ cartStore.itemCount }} items)</h2>
    
    <div v-for="item in cartStore.items" :key="item.id" class="cart-item">
      <span>{{ item.name }} - ¥{{ item.price }} x {{ item.quantity }}</span>
      <div>
        <button @click="updateQuantity(item.id, item.quantity - 1)">-</button>
        <button @click="updateQuantity(item.id, item.quantity + 1)">+</button>
        <button @click="removeItem(item.id)">删除</button>
      </div>
    </div>
    
    <div class="summary">
      <p>小计: ¥{{ cartStore.subtotal }}</p>
      <p>折扣: -¥{{ cartStore.discount }}</p>
      <p>总计: ¥{{ cartStore.total }}</p>
      <button @click="checkout" :disabled="cartStore.items.length === 0">
        结账
      </button>
    </div>
  </div>
</template>

<script setup>
import { useCartStore } from '@/stores/cart'

const cartStore = useCartStore()

const updateQuantity = (id, quantity) => {
  cartStore.updateQuantity(id, quantity)
}

const removeItem = (id) => {
  cartStore.removeItem(id)
}

const checkout = () => {
  alert(`订单总额: ¥${cartStore.total}`)
  cartStore.clearCart()
}
</script>

Pinia DevTools

Pinia与Vue DevTools深度集成,提供了强大的调试功能。

安装Vue DevTools

bash
# Chrome扩展
# 搜索 "Vue.js devtools"

# Firefox扩展
# 搜索 "Vue.js devtools"

调试Store

在DevTools中,你可以:

  • 查看当前状态
  • 检查状态变化历史
  • 提交状态变更
  • 时间旅行调试

总结

Pinia提供了一个现代化、直观的状态管理解决方案:

  1. 简单易用:API设计简洁,学习成本低
  2. TypeScript友好:提供完整的类型推断
  3. 开发工具支持:与Vue DevTools完美集成
  4. 模块化:支持多个独立的store
  5. 灵活:支持同步和异步操作

通过本章的学习,你应该能够:

  • 安装和配置Pinia
  • 创建和使用基本的store
  • 理解state、getters和actions的概念
  • 在组件中正确使用store

接下来的章节将深入探讨Pinia的高级特性。