Skip to content

Pinia 状态管理

Pinia 是 Vue3 的官方推荐状态管理库,它是 Vuex 的继任者,提供了更简洁的 API 和更好的 TypeScript 支持。

为什么使用 Pinia?

  • 简洁的 API:相比 Vuex,Pinia 的 API 更加简洁直观
  • 更好的 TypeScript 支持:内置 TypeScript 类型定义
  • 模块化设计:支持多个 store,每个 store 都是独立的
  • 响应式数据:使用 Vue3 的响应式系统
  • DevTools 支持:提供更好的开发工具集成

Pinia 安装

bash
npm install pinia

基本配置

在 main.js 中注册 Pinia

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

const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')

创建 Store

基本 Store

javascript
// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: '计数器'
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
    getCountWithName: (state) => {
      return `${state.name}: ${state.count}`
    }
  },
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
    reset() {
      this.count = 0
    },
    setName(name) {
      this.name = name
    }
  }
})

使用 Store

vue
<template>
  <div>
    <h2>{{ counterStore.getCountWithName }}</h2>
    <p>双倍计数: {{ counterStore.doubleCount }}</p>
    <button @click="counterStore.increment">增加</button>
    <button @click="counterStore.decrement">减少</button>
    <button @click="counterStore.reset">重置</button>
    <input v-model="name" @input="counterStore.setName(name)" placeholder="输入计数器名称">
  </div>
</template>

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

const counterStore = useCounterStore()
const name = ref(counterStore.name)
</script>

Store 的核心概念

State

State 是 Store 中存储数据的地方,它是一个函数,返回一个对象。

javascript
state: () => ({
  count: 0,
  user: null,
  items: []
})

Getters

Getters 是计算属性,用于从 State 中派生出新的数据。

javascript
getters: {
  // 基本用法
  doubleCount: (state) => state.count * 2,
  
  // 接收其他 getters
  getCountWithName: (state, getters) => {
    return `${state.name}: ${getters.doubleCount}`
  }
}

Actions

Actions 是用于修改 State 的方法,它可以是同步的,也可以是异步的。

javascript
actions: {
  // 同步 action
  increment() {
    this.count++
  },
  
  // 异步 action
  async fetchUser() {
    try {
      const response = await fetch('https://api.example.com/user')
      this.user = await response.json()
    } catch (error) {
      console.error('获取用户信息失败:', error)
    }
  }
}

多个 Store

Pinia 支持创建多个独立的 Store,每个 Store 负责管理不同的状态。

创建多个 Store

javascript
// stores/counter.js
export const useCounterStore = defineStore('counter', {
  // ...
})

// stores/user.js
export const useUserStore = defineStore('user', {
  state: () => ({
    user: null,
    loading: false,
    error: null
  }),
  actions: {
    async login(username, password) {
      this.loading = true
      this.error = null
      try {
        const response = await fetch('https://api.example.com/login', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ username, password })
        })
        
        if (!response.ok) {
          throw new Error('登录失败')
        }
        
        this.user = await response.json()
        localStorage.setItem('token', this.user.token)
      } catch (error) {
        this.error = error.message
      } finally {
        this.loading = false
      }
    },
    logout() {
      this.user = null
      localStorage.removeItem('token')
    }
  }
})

使用多个 Store

vue
<template>
  <div>
    <h2>计数器: {{ counterStore.count }}</h2>
    <button @click="counterStore.increment">增加</button>
    
    <div v-if="userStore.user">
      <h2>欢迎, {{ userStore.user.name }}</h2>
      <button @click="userStore.logout">退出登录</button>
    </div>
    <div v-else>
      <h2>登录</h2>
      <input v-model="username" placeholder="用户名">
      <input v-model="password" type="password" placeholder="密码">
      <button @click="userStore.login(username, password)" :disabled="userStore.loading">
        {{ userStore.loading ? '登录中...' : '登录' }}
      </button>
      <p v-if="userStore.error">{{ userStore.error }}</p>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useCounterStore } from './stores/counter'
import { useUserStore } from './stores/user'

const counterStore = useCounterStore()
const userStore = useUserStore()
const username = ref('')
const password = ref('')
</script>

数据持久化

使用 localStorage 实现数据持久化

javascript
// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: parseInt(localStorage.getItem('counter') || '0'),
    name: localStorage.getItem('counterName') || '计数器'
  }),
  actions: {
    increment() {
      this.count++
      this.saveToLocalStorage()
    },
    decrement() {
      this.count--
      this.saveToLocalStorage()
    },
    reset() {
      this.count = 0
      this.saveToLocalStorage()
    },
    setName(name) {
      this.name = name
      this.saveToLocalStorage()
    },
    saveToLocalStorage() {
      localStorage.setItem('counter', this.count.toString())
      localStorage.setItem('counterName', this.name)
    }
  }
})

使用插件实现数据持久化

也可以使用第三方插件如 pinia-plugin-persistedstate 来实现数据持久化:

bash
npm install pinia-plugin-persistedstate
javascript
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
app.use(pinia)
app.mount('#app')
javascript
// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: '计数器'
  }),
  // 启用持久化
  persist: true,
  // 或者自定义持久化配置
  // persist: {
  //   key: 'counter',
  //   storage: localStorage,
  //   paths: ['count'] // 只持久化 count 属性
  // },
  actions: {
    increment() {
      this.count++
    },
    // 其他 actions...
  }
})

实战示例

购物车 Store

javascript
// stores/cart.js
import { defineStore } from 'pinia'

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: [],
    total: 0
  }),
  getters: {
    itemCount: (state) => state.items.length,
    totalPrice: (state) => {
      return state.items.reduce((total, item) => {
        return total + item.price * item.quantity
      }, 0)
    }
  },
  actions: {
    addItem(item) {
      const existingItem = this.items.find(i => i.id === item.id)
      if (existingItem) {
        existingItem.quantity++
      } else {
        this.items.push({ ...item, quantity: 1 })
      }
      this.updateTotal()
    },
    removeItem(itemId) {
      this.items = this.items.filter(item => item.id !== itemId)
      this.updateTotal()
    },
    updateQuantity(itemId, quantity) {
      const item = this.items.find(i => i.id === itemId)
      if (item) {
        item.quantity = quantity
        this.updateTotal()
      }
    },
    clearCart() {
      this.items = []
      this.total = 0
    },
    updateTotal() {
      this.total = this.totalPrice
    }
  },
  persist: true
})

使用购物车 Store

vue
<template>
  <div>
    <h2>购物车</h2>
    
    <div v-if="cartStore.itemCount === 0">
      <p>购物车是空的</p>
    </div>
    <div v-else>
      <ul>
        <li v-for="item in cartStore.items" :key="item.id">
          <div>{{ item.name }}</div>
          <div>价格: ¥{{ item.price }}</div>
          <div>
            <button @click="cartStore.updateQuantity(item.id, item.quantity - 1)" :disabled="item.quantity <= 1">-</button>
            <span>{{ item.quantity }}</span>
            <button @click="cartStore.updateQuantity(item.id, item.quantity + 1)">+</button>
          </div>
          <button @click="cartStore.removeItem(item.id)">删除</button>
        </li>
      </ul>
      <div class="total">
        <h3>总计: ¥{{ cartStore.totalPrice }}</h3>
        <button @click="cartStore.clearCart">清空购物车</button>
      </div>
    </div>
    
    <h3>商品列表</h3>
    <ul>
      <li v-for="product in products" :key="product.id">
        <div>{{ product.name }}</div>
        <div>价格: ¥{{ product.price }}</div>
        <button @click="cartStore.addItem(product)">加入购物车</button>
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useCartStore } from './stores/cart'

const cartStore = useCartStore()
const products = ref([
  { id: 1, name: '商品1', price: 100 },
  { id: 2, name: '商品2', price: 200 },
  { id: 3, name: '商品3', price: 300 }
])
</script>

<style scoped>
.total {
  margin-top: 20px;
  padding: 10px;
  border-top: 1px solid #ccc;
}
</style>

通过本章的学习,我们掌握了 Pinia 的基本使用方法,包括创建 Store、使用 State、Getters 和 Actions,以及实现数据持久化等。Pinia 是 Vue3 推荐的状态管理库,它提供了简洁的 API 和良好的 TypeScript 支持,是构建复杂 Vue 应用的有力工具。

© 2026 编程马·菜鸟教程 版权所有