2026.07.26 — ARCHIVE — 3 MIN

Vue3 + Vite + Pinia + Router 实战总结

从 main.js 到组件通信,一次把 Vue3 前端工程的主流开发范式过一遍。


Vue3 + Vite + Pinia + Router 实战总结

学完后端就开始学前端了,说实话一开始挺懵的。

以前写的网页是 jQuery 那一套,DOM 随便操作,数据直接绑页面上。

换到 Vue3 之后,一切都变了,组件化、响应式、路由管理、状态管理,概念一堆。

但项目做完回头再看,发现其实核心逻辑就几块:

项目结构、路由、状态管理、API 封装、页面组件。

把这五块搞清楚,Vue3 项目就能写了。


先说说这套技术栈是什么

我们这个项目用的是目前主流的中后台开发组合:

技术 作用
Vite 构建工具,秒级启动,热更新
Vue 3 框架核心,组件化开发
Element Plus UI 组件库,按钮、表格、表单全有
Vue Router 路由管理,页面无刷新切换
Pinia 状态管理,跨组件共享数据
Axios HTTP 请求客户端
<script setup> 组合式 API 语法糖

光看名字可能觉得挺多的,但每个东西都有明确的职责,不会乱。


项目结构长什么样

一个标准的 Vue3 项目,目录结构大概是这样的:

src/
├── api/              # API 接口封装
│   ├── http.js       # Axios 实例 + 拦截器
│   ├── auth.js       # 登录相关接口
│   ├── emp.js        # 员工相关接口
│   └── dept.js       # 部门相关接口
├── router/           # 路由配置
│   └── index.js
├── stores/           # Pinia 状态管理
│   ├── user.js
│   └── emp.js
├── views/            # 页面组件
│   ├── login/
│   │   └── Login.vue
│   ├── layout/
│   │   ├── Layout.vue
│   │   └── Dashboard.vue
│   └── emp/
│       └── Emp.vue
├── App.vue           # 根容器
├── main.js           # 应用入口
└── index.html        # HTML 模板

每个目录负责单一职责,修改一处不会影响其他地方。


main.js:应用启动入口

main.js 是应用的起点,做的事情不多,就是创建 Vue 实例、注册插件、挂载到 DOM。

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(createPinia())
app.use(router)
app.use(ElementPlus)

app.mount('#app')

就这几行,顺序是固定的:先创建应用,再注册插件,最后挂载。


App.vue:最简根容器

App.vue 做的事最少,只有一个 <router-view>

<template>
  <div id="app">
    <router-view />
  </div>
</template>

它只负责渲染当前路由对应的组件,不含任何业务逻辑。


路由:页面切换的核心

路由配置在 router/index.js 里,用 Vue Router 4 实现单页面应用的路由管理。

import { createRouter, createWebHashHistory } from 'vue-router'

const routes = [
  {
    path: '/login',
    component: () => import('../views/login/Login.vue')
  },
  {
    path: '/',
    component: () => import('../views/layout/Layout.vue'),
    children: [
      { path: 'dashboard', component: () => import('../views/layout/Dashboard.vue') },
      { path: 'emp', component: () => import('../views/emp/Emp.vue') },
      { path: 'dept', component: () => import('../views/dept/Dept.vue') }
    ]
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

// 路由守卫:未登录跳转登录页
router.beforeEach((to, from, next) => {
  if (to.path !== '/login' && !localStorage.getItem('token')) {
    next('/login')
  } else {
    next()
  }
})

export default router

几个关键点:

  • 懒加载() => import(...) 按需加载,减小首屏体积
  • 嵌套路由:Layout 作为父路由,子路由在内部渲染
  • 路由守卫beforeEach 做权限校验

状态管理:Pinia 怎么用

Pinia 用来管理跨组件共享的状态,比如用户信息、登录态、列表数据等。

import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useUserStore = defineStore('user', {
  state: () => ({
    token: localStorage.getItem('token') || null,
    userInfo: null
  }),

  actions: {
    async login(username, password) {
      // 调用登录接口
      const result = await loginApi({ username, password })
      this.token = result.token
      this.userInfo = { id: result.id, name: result.name }
      localStorage.setItem('token', this.token)
      localStorage.setItem('userInfo', JSON.stringify(this.userInfo))
    },

    logout() {
      this.token = null
      this.userInfo = null
      localStorage.removeItem('token')
      localStorage.removeItem('userInfo')
    }
  }
})

在组件里使用:

<script setup>
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

async function handleLogin() {
  await userStore.login(form.username, form.password)
  router.push('/')
}
</script>

注意是调用函数 useUserStore(),不是 new


API 封装:Axios 拦截器

所有 HTTP 请求都经过 api/http.js 里的 Axios 实例,统一处理 Token 注入和错误提示。

import axios from 'axios'
import { ElMessage } from 'element-plus'

const api = axios.create({
  baseURL: '/api',
  timeout: 10000
})

// 请求拦截器:自动注入 Token
api.interceptors.request.use(config => {
  const token = localStorage.getItem('token')
  if (token) config.headers.token = token
  return config
})

// 响应拦截器:统一处理错误
api.interceptors.response.use(
  response => {
    const { code, msg, data } = response.data
    if (code === 1) return data
    ElMessage.error(msg || '操作失败')
    return Promise.reject(new Error(msg))
  },
  error => {
    if (error.response?.status === 401) {
      ElMessage.warning('请先登录')
      setTimeout(() => location.href = '/login', 1500)
    } else {
      ElMessage.error(error.message || '系统异常')
    }
    return Promise.reject(error)
  }
)

export default api

这样写的好处是:

  • 所有请求自动带 Token,不用每个组件都手动加
  • 错误统一处理,页面不需要重复写错误逻辑
  • 401 自动跳登录,体验更流畅

组件写法:<script setup>

Vue3 推荐用 Composition API 的 <script setup> 语法:

<script setup>
import { ref, onMounted } from 'vue'
import { useUserStore } from '@/stores/user'
import { empApi } from '@/api/emp'

const userStore = useUserStore()
const empList = ref([])
const loading = ref(false)

async function loadData() {
  loading.value = true
  try {
    empList.value = await empApi.list()
  } finally {
    loading.value = false
  }
}

onMounted(loadData)
</script>

<template>
  <el-table :data="empList" v-loading="loading">
    <el-table-column prop="name" label="姓名" />
    <el-table-column prop="phone" label="手机号" />
  </el-table>
</template>

ref 声明响应式数据,onMounted 在组件挂载时执行,代码比 Options API 简洁很多。


完整请求流程:从点击到渲染

以「查询员工列表」为例,整个流程是这样的:

用户点击查询按钮
  ↓
组件调用 store.action(如 empStore.list())
  ↓
Store 调用 API(empApi.list())
  ↓
Axios 请求拦截器注入 Token,发出请求
  ↓
后端返回数据
  ↓
Axios 响应拦截器处理成功/失败
  ↓
Store 更新 state(empStore.list = result)
  ↓
Vue 响应式系统自动触发组件重新渲染

数据变了,界面自动更新,不需要手动操作 DOM。

这就是 Vue3 响应式系统的核心。


一句话总结

Vue3 前端工程的核心就这几件事:

main.js 创建应用 → router 管理路由 → stores 管理状态 → api 封装请求 → views 写组件。

各层职责清晰,修改一处影响范围可控。

刚开始学的时候觉得东西多,写完一个项目之后发现也就那几块,套路固定,照着写就行。

End of plate