前端学习教程-Pinia 教程

Pinia 是 Vue 官方推荐的状态管理库,替代了传统的 Vuex,专为 Vue3 设计,支持 Composition API 和 TypeScript,API 更简洁,是目前 Vue 项目状态管理的最佳选择。

一、为什么选择 Pinia?

与 Vuex 相比,Pinia 具有以下优势:

  • 无需 mutations,直接在 actions 中修改状态
  • 完美支持 Vue3 的 Composition API
  • 内置 TypeScript 支持,类型推断更友好
  • 简化的 API,学习成本更低
  • 支持多个 Store 实例,无需嵌套模块
  • 更轻量(约 1KB)

二、安装 Pinia

在 Vue3 项目中安装 Pinia:

npm install pinia --save
# 或
yarn add pinia

三、初始化 Pinia

首先需要在项目入口文件中创建 Pinia 实例并挂载到 Vue 应用(main.js):

import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'  // 导入 createPinia

// 创建 Pinia 实例
const pinia = createPinia()

// 创建应用并使用 Pinia
createApp(App)
  .use(pinia)  // 挂载 Pinia
  .mount('#app')

四、核心概念与基础使用

1. 创建第一个 Store

Store 是 Pinia 的核心,用于存储全局状态。使用 defineStore() 创建 Store,它接收两个参数:

  • 第一个参数:Store 的唯一 ID(字符串)
  • 第二个参数:配置对象(包含 stategettersactions
import { defineStore } from 'pinia'

// 定义并导出 Store
export const useCounterStore = defineStore('counter', {
  // 状态:存储数据的地方
  state: () => ({
    count: 0,
    name: '计数器'
  }),

  // 计算属性:基于 state 派生的数据
  getters: {
    // 基本使用
    doubleCount: (state) => state.count * 2,
    
    // 使用其他 getters(通过 this)
    doubleCountPlusOne() {
      return this.doubleCount + 1
    },
    
    // 带参数的 getter(返回函数)
    getCountWithAdd: (state) => (num) => state.count + num
  },

  // 方法:处理业务逻辑(支持同步和异步)
  actions: {
    increment() {
      // 直接修改 state(无需 mutations)
      this.count++
    },
    
    decrement() {
      this.count--
    },
    
    // 带参数的 action
    incrementBy(num) {
      this.count += num
    },
    
    // 异步 action(如请求 API)
    async fetchData() {
      const res = await fetch('https://api.example.com/count')
      const data = await res.json()
      this.count = data.count
    }
  }
})

2. 在组件中使用 Store

在组件中通过创建的 useCounterStore 函数获取 Store 实例,然后访问其 stategettersactions

(1)使用 Composition API(推荐)

<template>
  <div class="counter">
    <h2>{{ counterStore.name }}</h2>
    <p>当前计数:{{ counterStore.count }}</p>
    <p>计数翻倍:{{ counterStore.doubleCount }}</p>
    <p>计数翻倍+1:{{ counterStore.doubleCountPlusOne }}</p>
    <p>计数+5:{{ counterStore.getCountWithAdd(5) }}</p>

    <button @click="counterStore.increment">+1</button>
    <button @click="counterStore.decrement">-1</button>
    <button @click="counterStore.incrementBy(10)">+10</button>
    <button @click="counterStore.fetchData">从API获取数据</button>
  </div>
</template>

<script setup>
// 导入 Store
import { useCounterStore } from '../stores/counterStore'

// 获取 Store 实例(注意:不要解构,会失去响应性)
const counterStore = useCounterStore()
</script>

(2)使用 Options API

如果使用 Options API,可以通过 mapStatemapGettersmapActions 辅助函数映射 Store:

<template>
  <div>
    <p>计数:{{ count }}</p>
    <p>翻倍:{{ doubleCount }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

<script>
import { useCounterStore } from '../stores/counterStore'
import { mapState, mapGetters, mapActions } from 'pinia'

export default {
  computed: {
    // 映射 state
    ...mapState(useCounterStore, ['count', 'name']),
    // 映射 getters
    ...mapGetters(useCounterStore, ['doubleCount'])
  },
  methods: {
    // 映射 actions
    ...mapActions(useCounterStore, ['increment', 'decrement'])
  }
}
</script>

五、修改 State 的多种方式

Pinia 允许通过多种方式修改 state,比 Vuex 更灵活:

1. 直接修改(最简单)

const counterStore = useCounterStore()
counterStore.count++  // 直接修改
counterStore.name = '新计数器'  // 直接修改

2. 使用 $patch 批量修改

适合同时修改多个状态:

// 对象形式
counterStore.$patch({
  count: 10,
  name: '批量修改后的计数器'
})

// 函数形式(更灵活,支持复杂逻辑)
counterStore.$patch((state) => {
  state.count += 5
  state.name = '函数修改的计数器'
})

3. 通过 actions 修改

适合包含业务逻辑的修改(推荐):

// 在 Store 中定义 action
actions: {
  resetAndSet(num) {
    this.count = 0  // 重置
    this.count += num  // 再修改
    this.name = '通过action修改'
  }
}

// 在组件中调用
counterStore.resetAndSet(10)

4. 替换整个 State

使用 $state 替换整个状态对象:

counterStore.$state = {
  count: 100,
  name: '全新状态'
}

六、异步 Actions 详解

Pinia 的 actions 支持异步操作(如 API 请求),无需像 Vuex 那样区分 actionsmutations

import { defineStore } from 'pinia'
import axios from 'axios'

export const useUserStore = defineStore('user', {
  state: () => ({
    userInfo: null,
    loading: false,
    error: null
  }),

  actions: {
    // 登录(异步操作)
    async login(username, password) {
      try {
        this.loading = true
        this.error = null
        
        // 调用 API
        const res = await axios.post('/api/login', {
          username,
          password
        })
        
        // 存储用户信息
        this.userInfo = res.data.user
        // 保存 token 到本地存储
        localStorage.setItem('token', res.data.token)
        
        return res.data  // 返回结果给组件
      } catch (err) {
        this.error = err.response?.data?.message || '登录失败'
        throw err  // 抛出错误,让组件可以捕获
      } finally {
        this.loading = false  // 无论成功失败,都结束加载
      }
    },
    
    // 退出登录
    logout() {
      this.userInfo = null
      localStorage.removeItem('token')
    },
    
    // 获取用户信息
    async fetchUserInfo() {
      try {
        this.loading = true
        const token = localStorage.getItem('token')
        const res = await axios.get('/api/user', {
          headers: { Authorization: `Bearer ${token}` }
        })
        this.userInfo = res.data
      } catch (err) {
        this.error = '获取用户信息失败'
      } finally {
        this.loading = false
      }
    }
  }
})

在组件中使用异步 action:

<script setup>
import { useUserStore } from '../stores/userStore'

const userStore = useUserStore()

const handleLogin = async () => {
  try {
    await userStore.login('admin', '123456')
    alert('登录成功')
  } catch (err) {
    alert(userStore.error)  // 显示错误信息
  }
}
</script>

七、Store 组合(跨 Store 调用)

在大型项目中,可能需要多个 Store 协同工作,Pinia 支持在一个 Store 中直接使用另一个 Store:

import { defineStore } from 'pinia'
import { useUserStore } from './userStore'  // 导入其他 Store

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: []
  }),

  getters: {
    totalPrice: (state) => {
      return state.items.reduce((total, item) => total + item.price * item.quantity, 0)
    },
    // 结合用户信息计算折扣价
    discountedPrice() {
      const userStore = useUserStore()  // 获取用户 Store
      // 会员打9折
      return userStore.userInfo?.isVip ? this.totalPrice * 0.9 : this.totalPrice
    }
  },

  actions: {
    addItem(product) {
      const userStore = useUserStore()
      // 未登录不能添加到购物车
      if (!userStore.userInfo) {
        throw new Error('请先登录')
      }
      
      this.items.push({ ...product, quantity: 1 })
    }
  }
})

八、持久化存储(状态持久化)

Pinia 本身不提供持久化功能,但可以通过 pinia-plugin-persistedstate 插件实现状态持久化(刷新页面后状态不丢失):

1. 安装插件

npm install pinia-plugin-persistedstate --save

2. 配置插件

// src/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'  // 导入插件
import App from './App.vue'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)  // 使用插件

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

3. 在 Store 中启用持久化

export const useUserStore = defineStore('user', {
  state: () => ({ /* ... */ }),
  // 启用持久化
  persist: true,  // 默认为 localStorage 存储整个 state
  // 或自定义配置
  persist: {
    key: 'user-storage',  // 存储的键名
    storage: sessionStorage,  // 使用 sessionStorage
    paths: ['userInfo']  // 只持久化 userInfo 字段
  }
})

九、最佳实践

  1. 按功能拆分 Store:避免创建单一的大 Store,按功能(如 userStorecartStoreproductStore)拆分
  2. 使用 Composition API:在组件中优先使用 setup() 语法,更符合 Vue3 生态
  3. 复杂逻辑放 actions:组件只负责调用 actions,业务逻辑封装在 Store 中
  4. 避免过度使用全局状态:只有多个组件共享的数据才需要放入 Store,组件内部数据用 ref/reactive
  5. TypeScript 类型定义:为 state 和 actions 参数添加类型,提升开发体验

总结

Pinia 是 Vue3 状态管理的最佳选择,其简洁的 API 和对 Composition API 的完美支持,让状态管理变得简单直观。通过本教程,你已掌握:

  • Pinia 的安装与初始化
  • Store 的创建(state、getters、actions)
  • 状态修改的多种方式
  • 异步 actions 的实现
  • Store 组合与持久化存储
posted @ 2025-10-04 22:42  碧水云天4  阅读(35)  评论(0)    收藏  举报