Vue 3\+Vite\+Pinia实战:前端工程化与组件化开发全指南

摘要:Vue 3作为前端主流框架,相比Vue 2,引入了Composition API、Teleport、Suspense等核心特性,大幅提升了代码的可维护性与扩展性;Vite作为新一代构建工具,凭借其基于ES Module的即时热更新、快速构建速度,替代Webpack成为Vue 3项目的首选构建工具;Pinia作为Vue官方推荐的状态管理库,替代Vuex,简化了状态管理逻辑,支持TypeScript、模块化开发。本文基于Vue 3.4、Vite 5.0、Pinia 2.1,详细讲解前端工程化配置、Composition API实战、组件化开发、状态管理、路由配置、打包优化等核心知识点,结合实战场景(后台管理系统),附完整项目结构与代码案例,帮助前端开发者快速掌握Vue 3+Vite+Pinia的实战技巧,突破前端工程化与组件化瓶颈,适合Vue入门到进阶的学习者、前端开发工程师。

一、前言:Vue 3+Vite+Pinia的核心价值与技术优势

随着前端技术的快速发展,工程化、组件化、类型安全成为前端开发的核心需求。Vue 3相比Vue 2,核心升级在于Composition API,解决了Vue 2 Options API在大型项目中代码复用困难、逻辑分散的问题;Vite相比Webpack,采用"按需编译"模式,避免了Webpack的全量打包,热更新速度提升10倍以上,构建效率大幅提升;Pinia相比Vuex,简化了状态管理流程,取消了Mutations、Modules的繁琐配置,支持TypeScript原生集成,更适合现代前端工程化开发。

三者结合,形成了"Vue 3(核心框架)+ Vite(构建工具)+ Pinia(状态管理)"的主流前端技术栈,广泛应用于后台管理系统、移动端应用、PC端官网等场景,能够大幅提升开发效率、代码质量与项目可维护性。本文聚焦实战,从项目初始化到工程化配置,再到组件化开发与状态管理,逐步讲解整套技术栈的落地技巧。

二、核心基础:项目初始化与工程化配置

2.1 项目初始化(Vue 3+Vite+Pinia+TypeScript)

bash 复制代码
# 1. 初始化Vite+Vue 3项目(TypeScript)
npm create vite@latest vue3-admin --template vue-ts

# 2. 进入项目目录
cd vue3-admin

# 3. 安装核心依赖
npm install pinia vue-router@4 axios element-plus @element-plus/icons-vue
# 安装开发依赖
npm install -D sass eslint prettier eslint-plugin-vue @types/node

# 4. 启动开发服务器(热更新)
npm run dev

# 5. 构建生产环境
npm run build

# 6. 预览生产环境打包结果
npm run preview

2.2 项目目录结构设计(工程化规范)

text 复制代码
vue3-admin/
├── public/               # 静态资源(图片、字体,无需打包)
├── src/
│   ├── assets/           # 静态资源(样式、图片,需打包)
│   │   ├── css/          # 全局样式
│   │   └── images/       # 图片资源
│   ├── components/       # 组件目录
│   │   ├── common/       # 通用组件(按钮、表单、分页、弹窗)
│   │   ├── layout/       # 布局组件(侧边栏、顶部导航、页脚)
│   │   └── business/     # 业务组件(对应具体业务模块)
│   ├── router/           # 路由配置(Vue Router 4)
│   │   └── index.ts      # 路由入口
│   ├── store/            # 状态管理(Pinia)
│   │   ├── modules/      # 模块化状态(用户、权限、设置)
│   │   └── index.ts      # Pinia初始化
│   ├── views/            # 页面组件(对应路由页面)
│   │   ├── Login/        # 登录页
│   │   ├── Home/         # 首页
│   │   ├── User/         # 用户管理页
│   │   └── Role/         # 角色管理页
│   ├── api/              # 接口请求封装(axios)
│   │   ├── request.ts    # axios配置(拦截器、基础路径)
│   │   ├── user.ts       # 用户相关接口
│   │   └── role.ts       # 角色相关接口
│   ├── utils/            # 工具函数
│   │   ├── auth.ts       # 权限工具(token存储、权限判断)
│   │   └── common.ts     # 通用工具(格式转换、防抖节流)
│   ├── types/            # TypeScript类型定义
│   ├── App.vue           # 根组件
│   ├── main.ts           # 入口文件(初始化Vue、Pinia、Router)
│   └── env.d.ts          # 环境变量类型定义
├── .eslintrc.js          # ESLint配置(代码规范)
├── .prettierrc.js        # Prettier配置(代码格式化)
├── vite.config.ts        # Vite配置(构建、开发服务器、别名)
├── tsconfig.json         # TypeScript配置
└── package.json          # 依赖配置

2.3 核心工程化配置(Vite+ESLint+Prettier)

工程化配置是保证项目规范、提升开发效率的关键,以下配置Vite别名、ESLint代码规范、Prettier代码格式化,确保团队开发规范统一。

typescript 复制代码
// 1. Vite配置(vite.config.ts)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  // 配置别名(简化路径引入)
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@assets': path.resolve(__dirname, './src/assets'),
      '@components': path.resolve(__dirname, './src/components'),
      '@views': path.resolve(__dirname, './src/views')
    }
  },
  // 开发服务器配置
  server: {
    port: 3000, // 端口号
    open: true, // 自动打开浏览器
    proxy: {
      // 接口代理(解决跨域)
      '/api': {
        target: 'http://localhost:8080', // 后端接口地址
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },
  // 构建配置
  build: {
    outDir: 'dist', // 打包输出目录
    sourcemap: false, // 关闭sourcemap(生产环境)
    // 打包优化:拆分代码块
    rollupOptions: {
      output: {
        chunkFileNames: 'js/[name]-[hash].js',
        entryFileNames: 'js/[name]-[hash].js',
        assetFileNames: '[ext]/[name]-[hash].[ext]'
      }
    }
  }
})

// 2. ESLint配置(.eslintrc.js)
module.exports = {
  root: true,
  env: {
    browser: true,
    es2021: true,
    node: true
  },
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-essential',
    'plugin:@typescript-eslint/recommended',
    'prettier' // 与Prettier集成,避免冲突
  ],
  parser: 'vue-eslint-parser',
  parserOptions: {
    ecmaVersion: 'latest',
    parser: '@typescript-eslint/parser',
    sourceType: 'module'
  },
  plugins: [
    'vue',
    '@typescript-eslint'
  ],
  rules: {
    // 自定义规则
    'vue/multi-word-component-names': 'off', // 关闭组件名多单词限制
    '@typescript-eslint/no-unused-vars': 'warn', // 未使用变量警告
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off' // 生产环境禁止console
  }
}

// 3. Prettier配置(.prettierrc.js)
module.exports = {
  printWidth: 120, // 每行最大长度
  tabWidth: 2, // 缩进宽度
  useTabs: false, // 使用空格缩进
  singleQuote: true, // 单引号
  semi: true, // 句末加分号
  trailingComma: 'es5', // 数组、对象末尾加逗号
  bracketSpacing: true, // 括号前后加空格
  arrowParens: 'always', // 箭头函数参数必须加括号
  proseWrap: 'never', // 不换行
  htmlWhitespaceSensitivity: 'ignore' // 忽略HTML空格敏感
}

三、实战模块:核心功能实战与落地

3.1 模块1:路由配置(Vue Router 4)

Vue Router 4是Vue 3的官方路由工具,支持路由懒加载、嵌套路由、路由守卫等功能,适合构建复杂的后台管理系统路由。

typescript 复制代码
// 1. 路由配置(src/router/index.ts)
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import { useUserStore } from '@/store/modules/user'
import Layout from '@/components/layout/Layout.vue'

// 路由规则
const routes: RouteRecordRaw[] = [
  // 登录页(无需布局)
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/Login/Login.vue'),
    meta: {
      title: '登录',
      requireAuth: false // 无需登录权限
    }
  },
  // 主布局路由(嵌套路由)
  {
    path: '/',
    component: Layout,
    meta: {
      requireAuth: true // 需要登录权限
    },
    children: [
      // 首页
      {
        path: '',
        name: 'Home',
        component: () => import('@/views/Home/Home.vue'),
        meta: {
          title: '首页'
        }
      },
      // 用户管理
      {
        path: '/user',
        name: 'User',
        component: () => import('@/views/User/User.vue'),
        meta: {
          title: '用户管理',
          permission: 'user:view' // 所需权限
        }
      },
      // 角色管理
      {
        path: '/role',
        name: 'Role',
        component: () => import('@/views/Role/Role.vue'),
        meta: {
          title: '角色管理',
          permission: 'role:view'
        }
      }
    ]
  },
  // 404页面
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('@/views/NotFound/NotFound.vue'),
    meta: {
      title: '页面不存在',
      requireAuth: false
    }
  }
]

// 创建路由实例
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes
})

// 路由守卫(全局前置守卫,判断登录与权限)
router.beforeEach((to, from, next) => {
  // 设置页面标题
  document.title = to.meta.title as string || 'Vue3后台管理系统'

  const userStore = useUserStore()
  // 判断是否需要登录
  if (to.meta.requireAuth) {
    // 未登录,跳转到登录页
    if (!userStore.token) {
      next({ name: 'Login', query: { redirect: to.fullPath } })
    } else {
      // 判断是否有权限
      const permission = to.meta.permission as string
      if (!permission || userStore.permissions.includes(permission)) {
        next()
      } else {
        // 无权限,跳转到首页
        next({ name: 'Home' })
      }
    }
  } else {
    next()
  }
})

export default router

3.2 模块2:状态管理(Pinia)

Pinia简化了状态管理逻辑,无需Mutations,直接通过Actions修改状态,支持模块化开发与TypeScript类型提示,以下实现用户状态管理(登录、退出、权限控制)。

typescript 复制代码
// 1. Pinia初始化(src/store/index.ts)
import { createPinia } from 'pinia'

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

export default pinia

// 2. 用户状态模块(src/store/modules/user.ts)
import { defineStore } from 'pinia'
import { login, getUserInfo, logout } from '@/api/user'
import { setToken, getToken, removeToken } from '@/utils/auth'

// 定义用户状态类型
interface UserState {
  token: string | null
  username: string
  avatar: string
  permissions: string[] // 权限列表
}

// 创建用户Store
export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    token: getToken(), // 从本地存储获取token
    username: '',
    avatar: '',
    permissions: []
  }),
  actions: {
    // 登录
    async loginAction(username: string, password: string) {
      const res = await login({ username, password })
      // 存储token
      this.token = res.token
      setToken(res.token)
      // 获取用户信息
      await this.getUserInfoAction()
    },
    // 获取用户信息
    async getUserInfoAction() {
      const res = await getUserInfo()
      this.username = res.username
      this.avatar = res.avatar
      this.permissions = res.permissions
    },
    // 退出登录
    async logoutAction() {
      await logout()
      // 清空状态
      this.token = null
      this.username = ''
      this.avatar = ''
      this.permissions = []
      // 移除本地token
      removeToken()
    }
  }
})

// 3. 权限工具(src/utils/auth.ts)
// 存储token到localStorage
export const setToken = (token: string) => {
  localStorage.setItem('token', token)
}

// 从localStorage获取token
export const getToken = () => {
  return localStorage.getItem('token')
}

// 移除token
export const removeToken = () => {
  localStorage.removeItem('token')
}

// 判断是否有权限
export const hasPermission = (permissions: string[], permission: string) => {
  return permissions.includes('admin') || permissions.includes(permission)
}

3.3 模块3:Composition API实战(组件化开发)

Composition API是Vue 3的核心特性,通过setup函数(或<script setup>语法糖)将逻辑聚合,实现代码复用,以下实现用户列表组件,结合Pinia、axios、Element Plus,实现查询、分页、删除等功能。

vue 复制代码
// 1. 用户列表组件(src/views/User/User.vue)
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { ElTable, ElTableColumn, ElPagination, ElButton, ElDialog, ElForm, ElFormItem, ElInput, ElMessage } from 'element-plus'
import { Delete, Edit, Search } from '@element-plus/icons-vue'
import { useUserStore } from '@/store/modules/user'
import { getUsers, deleteUser } from '@/api/user'

// 分页参数
const pageNum = ref(1)
const pageSize = ref(10)
const total = ref(0)
// 查询条件
const searchForm = ref({
  username: ''
})
// 用户列表数据
const userList = ref([])
// 加载状态
const loading = ref(false)

const userStore = useUserStore()

// 获取用户列表
const getUserList = async () => {
  loading.value = true
  try {
    const res = await getUsers({
      pageNum: pageNum.value,
      pageSize: pageSize.value,
      username: searchForm.value.username
    })
    userList.value = res.list
    total.value = res.total
  } catch (error) {
    ElMessage.error('获取用户列表失败,请稍后再试')
  } finally {
    loading.value = false
  }
}

// 分页切换
const handlePageChange = (num: number) => {
  pageNum.value = num
  getUserList()
}

// 每页条数切换
const handlePageSizeChange = (size: number) => {
  pageSize.value = size
  pageNum.value = 1
  getUserList()
}

// 搜索
const handleSearch = () => {
  pageNum.value = 1
  getUserList()
}

// 删除用户
const handleDelete = async (id: number) => {
  try {
    await deleteUser(id)
    ElMessage.success('删除用户成功')
    getUserList() // 重新获取列表
  } catch (error) {
    ElMessage.error('删除用户失败,请稍后再试')
  }
}

// 组件挂载时获取用户列表
onMounted(() => {
  getUserList()
})

// 计算属性:判断是否有删除权限
const hasDeletePermission = computed(() => {
  return userStore.permissions.includes('user:delete')
})
</script>

<template>
  <div class="user-container">
    <div class="search-bar">
      <ElForm :model="searchForm" inline>
        <ElFormItem label="用户名">
          <ElInput v-model="searchForm.username" placeholder="请输入用户名" />
        </ElFormItem>
        <ElFormItem>
          <ElButton type="primary" icon="<Search />" @click="handleSearch">搜索</ElButton>
        </ElFormItem>
      </ElForm>
    </div>

    <ElTable :data="userList" border loading="loading" style="width: 100%; margin-top: 16px;">
      <ElTableColumn label="ID" prop="id" align="center" />
      <ElTableColumn label="用户名" prop="username" align="center" />
      <ElTableColumn label="手机号" prop="phone" align="center" />
      <ElTableColumn label="角色" prop="roleName" align="center" />
      <ElTableColumn label="创建时间" prop="createTime" align="center" />
      <ElTableColumn label="操作" align="center">
        <template #default="scope">
          <ElButton type="text" icon="<Edit />">编辑</ElButton>
          <ElButton 
            type="text" 
            icon="<Delete />" 
            text-color="red" 
            @click="handleDelete(scope.row.id)"
            :disabled="!hasDeletePermission"
          >删除</ElButton>
        </template>
      </ElTableColumn>
    </ElTable>

    <div class="pagination" style="margin-top: 16px; text-align: right;">
      <ElPagination
        v-model:current-page="pageNum"
        v-model:page-size="pageSize"
        :total="total"
        @size-change="handlePageSizeChange"
        @current-change="handlePageChange"
        layout="total, sizes, prev, pager, next, jumper"
      />
    </div>
  </div>
</template>

<style scoped lang="scss">
.user-container {
  padding: 20px;
  .search-bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  .pagination {
    margin-top: 16px;
  }
}
</style>

3.4 模块4:接口请求封装(axios)

封装axios,实现请求拦截器(添加token)、响应拦截器(统一错误处理),简化接口调用,提升代码复用性。

typescript 复制代码
// 1. axios配置(src/api/request.ts)
import axios from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getToken, removeToken } from '@/utils/auth'
import { useUserStore } from '@/store/modules/user'

// 创建axios实例
const service = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL, // 环境变量(不同环境不同地址)
  timeout: 5000,
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  }
})

// 请求拦截器(添加token)
service.interceptors.request.use(
  (config) => {
    const token = getToken()
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

// 响应拦截器(统一错误处理)
service.interceptors.response.use(
  (response) => {
    const res = response.data
    // 状态码非200,视为错误
    if (res.code !== 200) {
      ElMessage.error(res.message || '请求失败')
      // 401:未登录或token过期,跳转登录页
      if (res.code === 401) {
        const userStore = useUserStore()
        ElMessageBox.confirm('登录已过期,请重新登录', '提示', {
          confirmButtonText: '重新登录',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          userStore.logoutAction()
          window.location.href = '/login'
        })
      }
      return Promise.reject(res)
    }
    return res
  },
  (error) => {
    // 网络错误处理
    ElMessage.error('网络异常,请检查网络连接')
    return Promise.reject(error)
  }
)

export default service

// 2. 用户接口封装(src/api/user.ts)
import request from './request'

// 登录
export const login = (data: { username: string; password: string }) => {
  return request({
    url: '/user/login',
    method: 'POST',
    data
  })
}

// 获取用户信息
export const getUserInfo = () => {
  return request({
    url: '/user/info',
    method: 'GET'
  })
}

// 退出登录
export const logout = () => {
  return request({
    url: '/user/logout',
    method: 'POST'
  })
}

// 获取用户列表
export const getUsers = (params: { pageNum: number; pageSize: number; username?: string }) => {
  return request({
    url: '/user/list',
    method: 'GET',
    params
  })
}

// 删除用户
export const deleteUser = (id: number) => {
  return request({
    url: `/user/${id}`,
    method: 'DELETE'
  })
}

四、打包优化与生产环境部署

4.1 打包优化技巧

Vite默认已做了基础优化,结合以下技巧,进一步提升生产环境打包性能,减少包体积,提升加载速度。

typescript 复制代码
// 1. 按需引入Element Plus(减少包体积)
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import pinia from './store'
// 按需引入Element Plus组件与样式
import { ElButton, ElTable, ElInput, ElPagination, ElDialog, ElForm, ElFormItem, ElMessage, ElMessageBox } from 'element-plus'
import 'element-plus/dist/index.css'
// 引入图标
import { Search, Delete, Edit } from '@element-plus/icons-vue'

const app = createApp(App)

// 注册组件
app.component('ElButton', ElButton)
app.component('ElTable', ElTable)
app.component('ElInput', ElInput)
app.component('ElPagination', ElPagination)
app.component('ElDialog', ElDialog)
app.component('ElForm', ElForm)
app.component('ElFormItem', ElFormItem)
// 注册图标
app.component('Search', Search)
app.component('Delete', Delete)
app.component('Edit', Edit)

// 全局挂载ElMessage、ElMessageBox
app.config.globalProperties.$message = ElMessage
app.config.globalProperties.$confirm = ElMessageBox.confirm

app.use(router).use(pinia).mount('#app')
相关推荐
Beginner x_u1 小时前
前端八股整理(手写 01)|Promise 超时控制、红绿灯与 Promise.all
前端·javascript·promise
周bro1 小时前
vue2+element ui 中的el-table表格 选中当前行当前行变色,单选/多选--------续集:表格样式修改整合
vue.js·ui·elementui
万少11 小时前
Vibe Coding不停歇,移动端 TRAE SOLO 让你用手机也能编程啦
前端·javascript·后端
kyriewen1111 小时前
WebAssembly:前端界的“外挂”,让C++代码在浏览器里跑起来
开发语言·前端·javascript·c++·单元测试·ecmascript
烛衔溟12 小时前
TypeScript 接口的基本使用 —— 定义对象形状
前端·javascript·typescript
铁皮饭盒13 小时前
成为AI全栈 - 第3课:路由 RESTful Elysia 状态码 设计规范
前端·后端·全栈
顾昂_13 小时前
Web 性能优化完全指南
前端·面试·性能优化
前端程序媛-Tian13 小时前
前端 AI 提效实战:从 0 到 1 打造团队专属 AI 代码评审工具
前端·人工智能·ai
支付宝体验科技13 小时前
Ant Design Pro v6.0.0 发布
前端