☰
Vue 3 工程实践:响应式解构、Axios 封装、路由与 Pinia 状态管理
2026/11/1 15:27:14 网站建设 项目流程

一、toRefs 与 toRef

reactive()返回一个响应式代理对象。直接解构代理对象时,解构出来的是普通值,响应式连接会丢失;toRefs()和toRef()可以把对象属性转换为与原对象保持同步的 ref。

import{reactive,toRefs,toRef}from'vue'constperson=reactive({name:'lqz',age:19})// 直接解构会失去响应式连接。const{name:plainName}=person// toRefs 为对象的每个属性创建 ref,并保持双向同步。const{name,age}=toRefs(person)age.value++console.log(person.age)// 20// 只需要一个属性时使用 toRef。constpersonName=toRef(person,'name')personName.value='新名字'console.log(person.name)

ref(person.name)只是用当前值创建一个新的 ref,之后与person.name没有自动关联;toRef(person, 'name')才是对原对象属性的引用。组合式函数返回响应式对象时,常使用toRefs让调用方可以安全解构。

<script setup> import { reactive, toRefs } from 'vue' const person = reactive({ name: 'lqz', age: 19 }) const { name, age } = toRefs(person) function increaseAge() { age.value++ } </script> <template> <p>{{ name }},{{ age }} 岁</p> <button @click="increaseAge">增加年龄</button> </template>

二、axios 基本使用

axios 是基于 Promise 的 HTTP 客户端。它的响应对象通常包含data、status、headers等信息,后端真正返回的业务数据一般位于response.data。

npminstallaxios

1. 请求电影列表

<script setup> import { reactive, ref, onMounted } from 'vue' import axios from 'axios' const films = reactive([]) const loading = ref(false) const error = ref('') async function loadFilms() { loading.value = true error.value = '' try { const { data } = await axios.get('http://127.0.0.1:8000/films/films/') // 假设接口格式为 { code: 0, results: [] }。 if (data.code !== 0) throw new Error(data.msg || '接口返回失败') films.splice(0, films.length, ...data.results) } catch (err) { console.error(err) error.value = '电影数据加载失败' } finally { loading.value = false } } onMounted(loadFilms) </script> <template> <p v-if="loading">加载中...</p> <p v-else-if="error">{{ error }}</p> <ul v-else> <li v-for="film in films" :key="film.id"> <h2>{{ film.name }}</h2> <img :src="film.poster" :alt="film.name" width="200"> <p>{{ film.synopsis }}</p> </li> </ul> </template>

当响应式数组使用reactive([])时,可以用splice原地更新,也可以直接重新赋值给ref([])。如果使用reactive({ results: [] }),则可以用Object.assign(state, data)更新对象的多个属性;不要把数组当成普通对象使用Object.assign后期待所有场景都清晰可读。

2. axios 配置项

常用配置可以分成请求地址、请求数据和请求行为三类:

axios({baseURL:'http://127.0.0.1:8000/api',url:'/films/',method:'get',params:{page:1,size:10},// 拼接到 URL 的查询参数headers:{Authorization:'Bearer token'},timeout:10000})axios.post('/login/',{username:'lqz',password:'secret'})

params用于查询字符串,data用于 POST、PUT、PATCH 等请求体。timeout可以防止请求无限等待,baseURL适合统一管理后端 API 前缀。

三、axios 实例与拦截器

项目中通常创建一个 axios 实例,将基础地址、超时、认证请求头和错误处理集中管理。请求拦截器在请求发出前执行,响应拦截器在then之前执行。

// src/utils/request.jsimportaxiosfrom'axios'constrequest=axios.create({baseURL:'http://127.0.0.1:8000/api/v1',timeout:10000})request.interceptors.request.use(config=>{consttoken=localStorage.getItem('access')if(token){config.headers.Authorization=`Bearer${token}`}returnconfig},error=>Promise.reject(error))request.interceptors.response.use(response=>{// 是否直接返回 data,取决于项目接口规范。returnresponse.data},error=>{if(error.response?.status===401){localStorage.removeItem('access')// 可以在这里触发路由跳转或统一提示。}returnPromise.reject(error)})exportdefaultrequest

拦截器应保持职责单一,不要在其中放置复杂业务逻辑。给请求额外添加自定义请求头可能触发 CORS 预检请求,后端需要允许对应的请求方法和请求头。

四、Promise、async 与 await

Promise 表示一个未来才会完成的异步操作,有pending、fulfilled和rejected三种状态。状态一旦从 pending 变为成功或失败,就不会再次改变。

consttask=newPromise((resolve,reject)=>{setTimeout(()=>{constsuccess=trueif(success)resolve('任务完成')elsereject(newError('任务失败'))},1000)})task.then(value=>console.log(value)).catch(error=>console.error(error)).finally(()=>console.log('任务结束'))

Promise 的回调不会阻塞浏览器主线程。then处理成功值,catch处理异常;在链式调用中,每个then的返回值都会成为下一个then的输入。

async函数总是返回 Promise,await会等待右侧 Promise 的结果,只能在 async 函数中使用(现代环境也支持顶层 await 的特定场景)。

asyncfunctionloadUser(){try{constresponse=awaitrequest.get('/user/')console.log(response)returnresponse}catch(error){console.error('请求失败',error)throwerror}}

多个互不依赖的请求不必串行等待,可以使用Promise.all并行发起:

const[user,settings]=awaitPromise.all([request.get('/user/'),request.get('/settings/')])

只有后一个请求依赖前一个结果时,才按顺序写多个await。这样可以减少不必要的等待,也能让错误边界更清晰。

五、Vue Router 4

Vue 3 使用 Vue Router 4。安装后通过createRouter和createWebHistory创建路由实例,再通过app.use(router)注册。

npminstallvue-router@4
// src/router/index.jsimport{createRouter,createWebHistory}from'vue-router'importHomeViewfrom'@/views/HomeView.vue'importLoginViewfrom'@/views/LoginView.vue'constroutes=[{path:'/',name:'home',component:HomeView},{path:'/login',name:'login',component:LoginView},{path:'/about/:id',name:'about',component:()=>import('@/views/AboutView.vue')}]exportdefaultcreateRouter({history:createWebHistory(import.meta.env.BASE_URL),routes})
// main.jsimport{createApp}from'vue'importAppfrom'./App.vue'importrouterfrom'./router'createApp(App).use(router).mount('#app')

组件模板中使用<router-link>进行声明式导航,页面组件显示在<router-view>中。JavaScript 中使用useRouter()获取路由实例,使用useRoute()获取当前路由对象。

<script setup> import { useRouter, useRoute } from 'vue-router' const router = useRouter() const route = useRoute() function goAbout() { router.push({ name: 'about', params: { id: 10 }, query: { from: 'home' } }) } </script> <template> <router-link to="/">首页</router-link> <button @click="goAbout">查看详情</button> <p>当前路径:{{ route.path }}</p> </template>

push会新增历史记录,replace会替换当前记录,back和go(-1)可以返回。路径参数使用route.params获取,查询参数使用route.query获取。

六、Vue Router 4 路由守卫

Vue Router 4 的全局守卫可以直接返回结果,不再强制使用 Vue Router 3 的next回调。守卫适合做登录状态检查,但接口权限仍必须由后端验证。

router.beforeEach(to=>{consttoken=localStorage.getItem('access')constrequiresAuth=to.matched.some(record=>record.meta.requiresAuth)if(requiresAuth&&!token){return{name:'login',query:{redirect:to.fullPath}}}if(to.name==='login'&&token){return{name:'home'}}returntrue})

路由通过meta.requiresAuth标记是否需要登录:

{path:'/admin',component:()=>import('@/views/AdminView.vue'),meta:{requiresAuth:true}}

要避免登录页本身被守卫重定向造成死循环。登录成功后读取redirect时,应确认它是本站路径,避免开放重定向。

七、Pinia 状态管理

Pinia 是 Vue 3 官方推荐的状态管理库。相比 Vuex,它的 API 更简洁,不需要强制区分 mutations 和 actions,支持更好的 TypeScript 推导,也可以按 store 拆分业务模块。

npminstallpinia
// src/stores/user.jsimport{computed,ref}from'vue'import{defineStore}from'pinia'exportconstuseUserStore=defineStore('user',()=>{constusername=ref('张三')constage=ref(19)consthobbies=ref(['阅读','音乐'])consthobbyCount=computed(()=>hobbies.value.length)functionincreaseAge(){age.value++}return{username,age,hobbies,hobbyCount,increaseAge}})
// main.jsimport{createApp}from'vue'import{createPinia}from'pinia'importAppfrom'./App.vue'createApp(App).use(createPinia()).mount('#app')
<script setup> import { useUserStore } from '@/stores/user' const userStore = useUserStore() </script> <template> <p>{{ userStore.username }}:{{ userStore.age }} 岁</p> <p>爱好数量:{{ userStore.hobbyCount }}</p> <button @click="userStore.increaseAge">增加年龄</button> </template>

只有跨组件或跨页面共享的数据才值得放入 Pinia。局部表单输入和临时显示状态放在组件内部,避免 store 变成无边界的全局变量集合。

八、Element Plus

Element Plus 是 Vue 3 对应的桌面端组件库;Vue 2 应使用 Element UI。安装后在入口文件注册,可以使用按钮、表格、表单、弹窗等组件。

npminstallelement-plus
// main.jsimport{createApp}from'vue'importElementPlusfrom'element-plus'import'element-plus/dist/index.css'importAppfrom'./App.vue'createApp(App).use(ElementPlus).mount('#app')
<template> <el-button type="primary" @click="showMessage">保存</el-button> </template> <script setup> import { ElMessage } from 'element-plus' function showMessage() { ElMessage.success('保存成功') } </script>

组件库可以提高开发效率,但仍应统一表单校验、错误提示和主题规范。大型项目可根据文档使用按需引入,减少最终打包体积。

九、总结

  1. toRefs适合解构整个响应式对象,toRef适合保持单个属性的响应式连接。
  2. axios 实例和拦截器可以统一基础地址、token、超时和错误处理;async/await让异步流程更清晰。
  3. Vue Router 4 使用createRouter和组合式 API,路由守卫可以控制前端导航,但不能替代后端权限。
  4. Pinia 是 Vue 3 推荐的状态管理库,按业务拆分 store,避免把所有状态都放到全局。
  5. Element Plus 与 Vue 3 配套,Element UI 与 Vue 2 配套,选择组件库时要先确认项目版本。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询