Vue 3 Pinia状态管理

Vue 3 Pinia状态管理

_

🎯 什么是Pinia?

Pinia是Vue 3的官方状态管理库,也是Vuex的继任者。它提供了更简洁、更直观的API,完全支持TypeScript,并且具有出色的开发体验。

与其他状态管理工具对比:

  • React: Redux

  • Vue 2: Vuex

  • Vue 3: Pinia(推荐)

🚀 Pinia的优势

  • ✅ 更简洁的API,无需mutations

  • ✅ 完整的TypeScript支持

  • ✅ 模块化设计,自动代码分割

  • ✅ 支持Vue DevTools调试

  • ✅ 没有魔法字符串,更安全

  • ✅ 支持组合式API和选项式API


📦 安装与配置

1. 安装Pinia

npm install pinia
# 或
yarn add pinia
# 或
pnpm add pinia

Vue 3 项目配置

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

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

// 创建Vue应用实例
const app = createApp(App)

// 注册Pinia插件
app.use(pinia)

// 挂载应用
app.mount('#app')

Vue 2 项目配置(兼容模式)

import { createPinia, PiniaVuePlugin } from 'pinia'
import Vue from 'vue'

// 注册插件
Vue.use(PiniaVuePlugin)

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

// 创建Vue实例
new Vue({
  el: '#app',
  pinia,
  // 其他配置...
})

🏗️ 创建Store

1. 项目结构

src/
├── stores/          # 存放所有store
│   ├── token.js    # token相关状态
│   ├── user.js     # 用户信息状态
│   └── teacher.js  # 教师数据状态
├── views/
└── components/

2. 选项式API定义Store

// stores/teacher.js
import { defineStore } from "pinia";

export const useTeacherStore = defineStore('teacher', {
  // state: 存储数据的函数
  state: () => ({
    teachers: [],
    currentTeacher: null,
    loading: false
  }),
  
  // getters: 计算属性,类似computed
  getters: {
    teacherCount: (state) => state.teachers.length,
    activeTeachers: (state) => state.teachers.filter(t => t.isActive),
    // 可以使用this访问其他getters
    formattedTeachers: (state) => {
      return state.teachers.map(teacher => ({
        ...teacher,
        fullName: `${teacher.firstName} ${teacher.lastName}`
      }))
    }
  },
  
  // actions: 修改状态的方法,支持异步
  actions: {
    async fetchTeachers() {
      this.loading = true;
      try {
        const response = await api.getTeachers();
        this.teachers = response.data;
      } catch (error) {
        console.error('获取教师列表失败:', error);
      } finally {
        this.loading = false;
      }
    },
    
    addTeacher(teacher) {
      this.teachers.push(teacher);
    },
    
    updateTeacher(id, updates) {
      const index = this.teachers.findIndex(t => t.id === id);
      if (index !== -1) {
        this.teachers[index] = { ...this.teachers[index], ...updates };
      }
    }
  }
});

3. 组合式API定义Store

// stores/student.js
import { defineStore } from "pinia";
import { ref, computed } from "vue";
import { getStudents } from "@/api/student";

export const useStudentStore = defineStore('student', () => {
  // state
  const students = ref([]);
  const loading = ref(false);
  const searchQuery = ref('');
  
  // getters
  const studentCount = computed(() => students.value.length);
  const filteredStudents = computed(() => {
    return students.value.filter(student => 
      student.name.toLowerCase().includes(searchQuery.value.toLowerCase())
    );
  });
  
  // actions
  const fetchStudents = async () => {
    loading.value = true;
    try {
      const response = await getStudents();
      students.value = response.data;
    } catch (error) {
      console.error('获取学生列表失败:', error);
    } finally {
      loading.value = false;
    }
  };
  
  const addStudent = (student) => {
    students.value.push(student);
  };
  
  const setSearchQuery = (query) => {
    searchQuery.value = query;
  };
  
  return {
    // state
    students,
    loading,
    searchQuery,
    // getters
    studentCount,
    filteredStudents,
    // actions
    fetchStudents,
    addStudent,
    setSearchQuery
  };
});

🔧 在组件中使用Store

1. 基本使用

import { useTeacherStore } from "@/stores/teacher.js";

export default {
  setup() {
    const teacherStore = useTeacherStore();
    
    // 访问state
    console.log(teacherStore.teachers);
    
    // 访问getters
    console.log(teacherStore.teacherCount);
    
    // 调用actions
    teacherStore.fetchTeachers();
    
    return {
      teacherStore
    };
  }
}

2. 数据修改的三种方法

import { useTeacherStore } from "@/stores/teacher.js";

const teacherStore = useTeacherStore();

// 方法1: 直接修改(简单场景)
teacherStore.teachers = [];

// 方法2: 通过action修改(推荐)
teacherStore.addTeacher({ name: '张老师', subject: '数学' });

// 方法3: 使用$patch批量修改
teacherStore.$patch({
  teachers: [],
  loading: false
});

// 方法4: $patch函数形式(支持逻辑)
teacherStore.$patch((state) => {
  state.teachers.push({ name: '李老师' });
  state.loading = true;
});

3. 使用storeToRefs简化访问

import { storeToRefs } from "pinia";
import { useTeacherStore } from "@/stores/teacher.js";

export default {
  setup() {
    const teacherStore = useTeacherStore();
    
    // ❌ 不推荐:直接访问会失去响应性
    // const { teachers, loading } = teacherStore;
    
    // ✅ 推荐:使用storeToRefs保持响应性
    const { teachers, loading, teacherCount } = storeToRefs(teacherStore);
    
    // actions不需要解构,直接使用
    const { fetchTeachers, addTeacher } = teacherStore;
    
    return {
      teachers,
      loading,
      teacherCount,
      fetchTeachers,
      addTeacher
    };
  }
}

🔐 Token管理实例

1. Token Store定义

// stores/token.js
import { defineStore } from "pinia";

export const useTokenStore = defineStore("token", {
  state: () => ({
    token: localStorage.getItem("token") || "",
    refreshToken: localStorage.getItem("refreshToken") || "",
    userInfo: JSON.parse(localStorage.getItem("userInfo") || "{}")
  }),
  
  getters: {
    isAuthenticated: (state) => !!state.token,
    isAdmin: (state) => state.userInfo.role === 'admin',
    userName: (state) => state.userInfo.name || '未登录用户'
  },
  
  actions: {
    setToken(token) {
      this.token = token;
      localStorage.setItem("token", token);
    },
    
    setRefreshToken(refreshToken) {
      this.refreshToken = refreshToken;
      localStorage.setItem("refreshToken", refreshToken);
    },
    
    setUserInfo(userInfo) {
      this.userInfo = userInfo;
      localStorage.setItem("userInfo", JSON.stringify(userInfo));
    },
    
    // 登录
    async login(credentials) {
      try {
        const response = await api.login(credentials);
        this.setToken(response.data.token);
        this.setRefreshToken(response.data.refreshToken);
        this.setUserInfo(response.data.user);
        return response.data;
      } catch (error) {
        throw error;
      }
    },
    
    // 登出
    logout() {
      this.token = "";
      this.refreshToken = "";
      this.userInfo = {};
      localStorage.removeItem("token");
      localStorage.removeItem("refreshToken");
      localStorage.removeItem("userInfo");
    },
    
    // 检查token有效性
    checkToken() {
      return this.token && this.token !== "null";
    }
  }
});

2. 在组件中使用Token

import { useTokenStore } from "@/stores/token";
import { storeToRefs } from "pinia";
import { ElMessage } from "element-plus";

export default {
  setup() {
    const tokenStore = useTokenStore();
    const { isAuthenticated, userName, isAdmin } = storeToRefs(tokenStore);
    
    // 检查权限
    function checkAdminPermission() {
      if (!isAdmin.value) {
        ElMessage.error("权限不足");
        return false;
      }
      return true;
    }
    
    // 登出
    function handleLogout() {
      tokenStore.logout();
      ElMessage.success("注销成功");
      // 跳转到登录页
      router.push('/login');
    }
    
    // 检查token状态
    function validateToken() {
      return tokenStore.checkToken();
    }
    
    return {
      isAuthenticated,
      userName,
      isAdmin,
      checkAdminPermission,
      handleLogout,
      validateToken
    };
  }
}

📊 监听Store变化

1. 使用$subscribe监听

import { useTeacherStore } from "@/stores/teacher.js";

const teacherStore = useTeacherStore();

// 监听store变化
const unsubscribe = teacherStore.$subscribe((mutation, state) => {
  console.log("Store发生变化:", mutation);
  console.log("新状态:", state);
  
  // 可以在这里做持久化等操作
  localStorage.setItem('teacherData', JSON.stringify(state.teachers));
});

// 组件卸载时取消监听
onUnmounted(() => {
  unsubscribe();
});

2. 使用$onAction监听Action

const teacherStore = useTeacherStore();

// 监听action调用
const unsubscribeAction = teacherStore.$onAction(
  ({
    name, // action名称
    store, // store实例
    args, // 参数
    after, // action完成后的回调
    onError // action出错的回调
  }) => {
    console.log(`正在调用action: ${name}`);
    
    after((result) => {
      console.log(`${name} 执行完成,返回:`, result);
    });
    
    onError((error) => {
      console.error(`${name} 执行失败:`, error);
    });
  }
);


💡 最佳实践

1. Store拆分原则

  • 按功能模块拆分(用户、产品、订单等)

  • 避免过于庞大的单个store

  • 相关数据放在同一个store中

2. 命名规范

// ✅ 好的命名
const useUserStore = defineStore('user', {});
const useProductStore = defineStore('product', {});

// ❌ 避免的命名
const store1 = defineStore('store1', {});
const data = defineStore('data', {});

3. 类型安全(TypeScript)

// stores/user.ts
interface UserInfo {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

export const useUserStore = defineStore('user', {
  state: () => ({
    user: null as UserInfo | null,
    loading: false as boolean
  }),
  
  getters: {
    userName: (state): string => state.user?.name || '未登录',
    isAdmin: (state): boolean => state.user?.role === 'admin'
  },
  
  actions: {
    async setUser(user: UserInfo): Promise<void> {
      this.user = user;
      localStorage.setItem('user', JSON.stringify(user));
    }
  }
});

4. 性能优化

  • 合理使用storeToRefs避免不必要的响应式

  • 大型数据使用computed进行派生

  • 及时取消订阅避免内存泄漏


📝 总结

Pinia作为Vue 3的官方状态管理方案,提供了:

  1. 🔧 简洁的API: 无需mutations,更直观

  2. 📝 类型安全: 原生TypeScript支持

  3. 🏗️ 模块化: 自动代码分割,按需加载

  4. 🔄 响应性: 完美集成Vue 3响应式系统

  5. 🛠️ 开发体验: 优秀的DevTools支持

通过合理使用Pinia,可以让Vue应用的状态管理变得简单、高效、可维护!

JS-回调函数 2025-02-05
Vue Router路由导航守卫 2024-11-18

评论区