MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue Pinia 实际项目中的典型应用场景与案例分享

2023-07-206.5k 阅读

Vue Pinia 简介

在深入探讨 Vue Pinia 在实际项目中的应用场景与案例之前,我们先来简要回顾一下 Pinia 是什么。Pinia 是 Vue.js 的新一代状态管理库,它基于 Vue 3 的 Composition API 构建,为 Vue 应用提供了一种简单、直观且高效的状态管理方式。与 Vuex 相比,Pinia 有着更简洁的 API 设计,同时保留了诸如状态共享、数据持久化等重要特性。

Pinia 的核心概念包括 Store(存储),它是包含状态、getter 和 action 的容器。状态(state)类似于组件中的 data,用于存储应用的数据;getter 类似于计算属性,用于从状态派生新的数据;action 类似于组件中的方法,用于包含业务逻辑,它可以同步或异步地修改状态。

典型应用场景

多页面状态共享

在一个大型的单页应用(SPA)中,往往存在多个页面需要共享某些状态。例如,一个电商应用中,用户的登录状态、购物车信息等需要在不同页面之间保持一致。

  • 代码示例: 首先,创建一个 Pinia Store 来管理用户登录状态。
    import { defineStore } from 'pinia';
    
    export const useUserStore = defineStore('user', {
      state: () => ({
        isLoggedIn: false,
        userInfo: null
      }),
      getters: {
        isAuthenticated: (state) => state.isLoggedIn && state.userInfo!== null
      },
      actions: {
        login(user) {
          this.isLoggedIn = true;
          this.userInfo = user;
        },
        logout() {
          this.isLoggedIn = false;
          this.userInfo = null;
        }
      }
    });
    
    在登录页面组件中,可以这样使用:
    <template>
      <div>
        <button @click="handleLogin">登录</button>
      </div>
    </template>
    
    <script setup>
    import { useUserStore } from '@/stores/user';
    
    const userStore = useUserStore();
    const handleLogin = () => {
      const user = { name: 'John Doe', email: 'johndoe@example.com' };
      userStore.login(user);
    };
    </script>
    
    在需要验证用户登录状态的其他页面组件中:
    <template>
      <div>
        <div v-if="userStore.isAuthenticated">欢迎,{{ userStore.userInfo.name }}</div>
        <div v-else>请先登录</div>
      </div>
    </template>
    
    <script setup>
    import { useUserStore } from '@/stores/user';
    
    const userStore = useUserStore();
    </script>
    
    通过这种方式,不同页面组件可以方便地共享和修改用户登录状态,确保整个应用状态的一致性。

复杂业务逻辑封装

当应用中有复杂的业务逻辑,且这些逻辑涉及多个组件之间的交互时,Pinia 可以将这些逻辑封装在 Store 中,提高代码的可维护性和复用性。

  • 代码示例: 假设我们有一个任务管理应用,任务之间存在父子关系,并且有任务的排序、筛选等复杂逻辑。
    import { defineStore } from 'pinia';
    
    export const useTaskStore = defineStore('task', {
      state: () => ({
        tasks: [
          { id: 1, title: '任务1', parentId: null },
          { id: 2, title: '任务2', parentId: 1 },
          { id: 3, title: '任务3', parentId: null }
        ]
      }),
      getters: {
        getTaskById: (state) => (id) => state.tasks.find(task => task.id === id),
        getChildTasks: (state) => (parentId) => state.tasks.filter(task => task.parentId === parentId)
      },
      actions: {
        addTask(task) {
          this.tasks.push(task);
        },
        sortTasks() {
          this.tasks.sort((a, b) => a.title.localeCompare(b.title));
        },
        filterTasksByTitle(title) {
          return this.tasks.filter(task => task.title.includes(title));
        }
      }
    });
    
    在任务列表组件中,可以使用这些逻辑:
    <template>
      <div>
        <input v-model="filterTitle" placeholder="筛选任务">
        <ul>
          <li v-for="task in filteredTasks" :key="task.id">{{ task.title }}</li>
        </ul>
        <button @click="sortTasks">排序任务</button>
      </div>
    </template>
    
    <script setup>
    import { useTaskStore } from '@/stores/task';
    
    const taskStore = useTaskStore();
    const filterTitle = ref('');
    const filteredTasks = computed(() => taskStore.filterTasksByTitle(filterTitle.value));
    const sortTasks = () => taskStore.sortTasks();
    </script>
    
    这样,复杂的任务管理逻辑被封装在 Store 中,各个组件可以方便地调用,使得组件代码更加简洁,业务逻辑也更加集中。

数据持久化

在一些应用中,需要将部分状态数据持久化到本地存储(如 localStorage 或 sessionStorage),以便在页面刷新或重新打开应用时,能够恢复之前的状态。

  • 代码示例: 以购物车为例,我们希望购物车数据在页面刷新后依然存在。
    import { defineStore } from 'pinia';
    
    export const useCartStore = defineStore('cart', {
      state: () => ({
        items: []
      }),
      getters: {
        totalPrice: (state) => state.items.reduce((total, item) => total + item.price * item.quantity, 0)
      },
      actions: {
        addToCart(item) {
          const existingItem = this.items.find(i => i.id === item.id);
          if (existingItem) {
            existingItem.quantity++;
          } else {
            this.items.push({...item, quantity: 1 });
          }
          this.saveToLocalStorage();
        },
        removeFromCart(id) {
          this.items = this.items.filter(item => item.id!== id);
          this.saveToLocalStorage();
        },
        loadFromLocalStorage() {
          const data = localStorage.getItem('cart');
          if (data) {
            this.items = JSON.parse(data);
          }
        },
        saveToLocalStorage() {
          localStorage.setItem('cart', JSON.stringify(this.items));
        }
      }
    });
    
    在购物车组件中:
    <template>
      <div>
        <button @click="addToCart({ id: 1, title: '商品1', price: 10 }">添加到购物车</button>
        <div>总价: {{ cartStore.totalPrice }}</div>
      </div>
    </template>
    
    <script setup>
    import { useCartStore } from '@/stores/cart';
    
    const cartStore = useCartStore();
    cartStore.loadFromLocalStorage();
    const addToCart = (item) => cartStore.addToCart(item);
    </script>
    
    通过在 Store 中添加数据持久化的逻辑,购物车数据可以在页面刷新或关闭后依然保持。

案例分享

在线教育平台

  1. 项目背景 我们开发了一个在线教育平台,学生可以在平台上注册、登录,浏览课程,将感兴趣的课程添加到收藏夹,购买课程等。平台有多个页面,如首页、课程列表页、课程详情页、用户中心等。
  2. 使用 Pinia 的场景
  • 用户状态管理
    • 使用 Pinia Store 来管理用户的登录状态、用户信息等。这样在不同页面都能方便地判断用户是否登录,以及获取用户的相关信息。
    • 代码实现:
      import { defineStore } from 'pinia';
      
      export const useUserStore = defineStore('user', {
        state: () => ({
          isLoggedIn: false,
          userInfo: null
        }),
        getters: {
          isAuthenticated: (state) => state.isLoggedIn && state.userInfo!== null
        },
        actions: {
          login(user) {
            this.isLoggedIn = true;
            this.userInfo = user;
          },
          logout() {
            this.isLoggedIn = false;
            this.userInfo = null;
          }
        }
      });
      
      在登录组件中:
      <template>
        <div>
          <input v-model="username" placeholder="用户名">
          <input v-model="password" placeholder="密码">
          <button @click="handleLogin">登录</button>
        </div>
      </template>
      
      <script setup>
      import { useUserStore } from '@/stores/user';
      import { ref } from 'vue';
      
      const userStore = useUserStore();
      const username = ref('');
      const password = ref('');
      const handleLogin = () => {
        // 这里模拟登录请求
        const user = { name: username.value, email: 'test@example.com' };
        userStore.login(user);
      };
      </script>
      
      在需要显示用户信息的组件中:
      <template>
        <div>
          <div v-if="userStore.isAuthenticated">欢迎,{{ userStore.userInfo.name }}</div>
          <div v-else><a href="/login">请登录</a></div>
        </div>
      </template>
      
      <script setup>
      import { useUserStore } from '@/stores/user';
      
      const userStore = useUserStore();
      </script>
      
  • 课程收藏管理
    • 用户可以在课程列表页和课程详情页将课程添加到收藏夹。收藏夹状态需要在不同页面共享,并且可以进行持久化存储。
    • 代码实现:
      import { defineStore } from 'pinia';
      
      export const useFavoriteStore = defineStore('favorite', {
        state: () => ({
          courses: []
        }),
        getters: {
          getFavoriteCourses: (state) => state.courses
        },
        actions: {
          addCourseToFavorite(course) {
            this.courses.push(course);
            this.saveToLocalStorage();
          },
          removeCourseFromFavorite(courseId) {
            this.courses = this.courses.filter(course => course.id!== courseId);
            this.saveToLocalStorage();
          },
          loadFromLocalStorage() {
            const data = localStorage.getItem('favoriteCourses');
            if (data) {
              this.courses = JSON.parse(data);
            }
          },
          saveToLocalStorage() {
            localStorage.setItem('favoriteCourses', JSON.stringify(this.courses));
          }
        }
      });
      
      在课程列表组件中:
      <template>
        <div>
          <ul>
            <li v-for="course in courses" :key="course.id">
              {{ course.title }}
              <button @click="addToFavorite(course)">收藏</button>
            </li>
          </ul>
        </div>
      </template>
      
      <script setup>
      import { useFavoriteStore } from '@/stores/favorite';
      import { ref } from 'vue';
      
      const favoriteStore = useFavoriteStore();
      const courses = ref([
        { id: 1, title: 'Vue 基础课程' },
        { id: 2, title: 'React 入门课程' }
      ]);
      const addToFavorite = (course) => favoriteStore.addCourseToFavorite(course);
      favoriteStore.loadFromLocalStorage();
      </script>
      
      在用户中心的收藏夹页面组件中:
      <template>
        <div>
          <h2>我的收藏课程</h2>
          <ul>
            <li v-for="course in favoriteStore.getFavoriteCourses" :key="course.id">{{ course.title }}</li>
          </ul>
        </div>
      </template>
      
      <script setup>
      import { useFavoriteStore } from '@/stores/favorite';
      
      const favoriteStore = useFavoriteStore();
      favoriteStore.loadFromLocalStorage();
      </script>
      
  • 购物车管理
    • 与前面购物车的案例类似,用户在课程详情页可以将课程添加到购物车,购物车数据需要在不同页面共享,并且实现数据持久化。
    • 代码实现:
      import { defineStore } from 'pinia';
      
      export const useCartStore = defineStore('cart', {
        state: () => ({
          courses: []
        }),
        getters: {
          totalPrice: (state) => state.courses.reduce((total, course) => total + course.price, 0)
        },
        actions: {
          addCourseToCart(course) {
            this.courses.push(course);
            this.saveToLocalStorage();
          },
          removeCourseFromCart(courseId) {
            this.courses = this.courses.filter(course => course.id!== courseId);
            this.saveToLocalStorage();
          },
          loadFromLocalStorage() {
            const data = localStorage.getItem('cartCourses');
            if (data) {
              this.courses = JSON.parse(data);
            }
          },
          saveToLocalStorage() {
            localStorage.setItem('cartCourses', JSON.stringify(this.courses));
          }
        }
      });
      
      在课程详情页组件中:
      <template>
        <div>
          <h2>{{ course.title }}</h2>
          <p>价格: {{ course.price }}</p>
          <button @click="addToCart(course)">加入购物车</button>
        </div>
      </template>
      
      <script setup>
      import { useCartStore } from '@/stores/cart';
      import { ref } from 'vue';
      
      const cartStore = useCartStore();
      const course = ref({ id: 1, title: '高级 JavaScript 课程', price: 50 });
      const addToCart = (course) => cartStore.addCourseToCart(course);
      cartStore.loadFromLocalStorage();
      </script>
      
      在购物车页面组件中:
      <template>
        <div>
          <h2>购物车</h2>
          <ul>
            <li v-for="course in cartStore.courses" :key="course.id">{{ course.title }} - {{ course.price }}</li>
          </ul>
          <div>总价: {{ cartStore.totalPrice }}</div>
        </div>
      </template>
      
      <script setup>
      import { useCartStore } from '@/stores/cart';
      
      const cartStore = useCartStore();
      cartStore.loadFromLocalStorage();
      </script>
      

企业内部管理系统

  1. 项目背景 为某企业开发内部管理系统,包括员工信息管理、任务分配管理、文件共享等功能。不同角色的员工(如经理、普通员工)有不同的权限,系统有多个页面和复杂的业务逻辑。
  2. 使用 Pinia 的场景
  • 权限管理
    • 根据用户角色确定不同的页面访问权限和操作权限。通过 Pinia Store 管理权限状态,使得在各个组件中都能方便地进行权限判断。
    • 代码实现:
      import { defineStore } from 'pinia';
      
      export const useAuthStore = defineStore('auth', {
        state: () => ({
          role: null,
          permissions: []
        }),
        getters: {
          hasPermission: (state) => (permission) => state.permissions.includes(permission)
        },
        actions: {
          setRole(role) {
            this.role = role;
            // 根据角色设置权限
            if (role ==='manager') {
              this.permissions = ['view_all_tasks', 'assign_task', 'edit_employee_info'];
            } else {
              this.permissions = ['view_own_tasks'];
            }
          }
        }
      });
      
      在页面组件中判断权限:
      <template>
        <div>
          <button v-if="authStore.hasPermission('assign_task')" @click="assignTask">分配任务</button>
        </div>
      </template>
      
      <script setup>
      import { useAuthStore } from '@/stores/auth';
      
      const authStore = useAuthStore();
      const assignTask = () => {
        // 分配任务逻辑
      };
      </script>
      
  • 任务管理
    • 任务有不同的状态(待办、进行中、已完成),并且任务之间可能存在依赖关系。通过 Pinia Store 封装任务管理的逻辑,包括任务的创建、更新、删除以及状态变更等。
    • 代码实现:
      import { defineStore } from 'pinia';
      
      export const useTaskStore = defineStore('task', {
        state: () => ({
          tasks: []
        }),
        getters: {
          getTasksByStatus: (state) => (status) => state.tasks.filter(task => task.status === status),
          getTaskDependencies: (state) => (taskId) => {
            const task = state.tasks.find(t => t.id === taskId);
            return task? state.tasks.filter(t => task.dependencies.includes(t.id)) : [];
          }
        },
        actions: {
          createTask(task) {
            this.tasks.push(task);
          },
          updateTask(taskId, updatedTask) {
            const index = this.tasks.findIndex(task => task.id === taskId);
            if (index!== -1) {
              this.tasks[index] = {...this.tasks[index],...updatedTask };
            }
          },
          deleteTask(taskId) {
            this.tasks = this.tasks.filter(task => task.id!== taskId);
          },
          changeTaskStatus(taskId, newStatus) {
            this.updateTask(taskId, { status: newStatus });
          }
        }
      });
      
      在任务列表组件中:
      <template>
        <div>
          <ul>
            <li v-for="task in tasks" :key="task.id">
              {{ task.title }} - {{ task.status }}
              <button @click="changeTaskStatus(task.id, 'in_progress')">开始任务</button>
            </li>
          </ul>
        </div>
      </template>
      
      <script setup>
      import { useTaskStore } from '@/stores/task';
      
      const taskStore = useTaskStore();
      const tasks = computed(() => taskStore.getTasksByStatus('to_do'));
      const changeTaskStatus = (taskId, newStatus) => taskStore.changeTaskStatus(taskId, newStatus);
      </script>
      
  • 文件共享状态管理
    • 员工可以上传、下载文件,并且需要记录文件的上传者、上传时间等信息。通过 Pinia Store 管理文件共享相关的状态,方便在不同页面展示文件列表和文件详情。
    • 代码实现:
      import { defineStore } from 'pinia';
      
      export const useFileStore = defineStore('file', {
        state: () => ({
          files: []
        }),
        getters: {
          getFilesByUser: (state) => (userId) => state.files.filter(file => file.uploaderId === userId)
        },
        actions: {
          uploadFile(file) {
            this.files.push(file);
          },
          downloadFile(fileId) {
            // 模拟下载逻辑
            const file = this.files.find(f => f.id === fileId);
            if (file) {
              console.log(`下载文件: ${file.name}`);
            }
          }
        }
      });
      
      在文件上传组件中:
      <template>
        <div>
          <input type="file" @change="handleFileUpload">
        </div>
      </template>
      
      <script setup>
      import { useFileStore } from '@/stores/file';
      import { ref } from 'vue';
      
      const fileStore = useFileStore();
      const handleFileUpload = (e) => {
        const file = e.target.files[0];
        const fileInfo = { id: Date.now(), name: file.name, uploaderId: 1 /* 假设用户 ID */ };
        fileStore.uploadFile(fileInfo);
      };
      </script>
      
      在文件列表组件中:
      <template>
        <div>
          <ul>
            <li v-for="file in files" :key="file.id">{{ file.name }} - 上传者: {{ file.uploaderId }}</li>
          </ul>
        </div>
      </template>
      
      <script setup>
      import { useFileStore } from '@/stores/file';
      
      const fileStore = useFileStore();
      const files = computed(() => fileStore.files);
      </script>
      

通过以上在实际项目中的典型应用场景和案例分享,可以看出 Vue Pinia 在状态管理方面的强大功能和灵活性,它能够有效地提高前端应用的开发效率和可维护性,使代码结构更加清晰,逻辑更加集中。无论是小型项目还是大型复杂项目,Pinia 都能为开发者提供良好的状态管理解决方案。