Vue 3 工程实践:响应式解构、Axios 封装、路由与 Pinia 状态管理 一、toRefs 与 toRefreactive()返回一个响应式代理对象。直接解构代理对象时解构出来的是普通值响应式连接会丢失toRefs()和toRef()可以把对象属性转换为与原对象保持同步的 ref。import{reactive,toRefs,toRef}fromvueconstpersonreactive({name:lqz,age:19})// 直接解构会失去响应式连接。const{name:plainName}person// toRefs 为对象的每个属性创建 ref并保持双向同步。const{name,age}toRefs(person)age.valueconsole.log(person.age)// 20// 只需要一个属性时使用 toRef。constpersonNametoRef(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 clickincreaseAge增加年龄/button /template二、axios 基本使用axios 是基于 Promise 的 HTTP 客户端。它的响应对象通常包含data、status、headers等信息后端真正返回的业务数据一般位于response.data。npminstallaxios1. 请求电影列表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-ifloading加载中.../p p v-else-iferror{{ error }}/p ul v-else li v-forfilm in films :keyfilm.id h2{{ film.name }}/h2 img :srcfilm.poster :altfilm.name width200 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.jsimportaxiosfromaxiosconstrequestaxios.create({baseURL:http://127.0.0.1:8000/api/v1,timeout:10000})request.interceptors.request.use(config{consttokenlocalStorage.getItem(access)if(token){config.headers.AuthorizationBearer${token}}returnconfig},errorPromise.reject(error))request.interceptors.response.use(response{// 是否直接返回 data取决于项目接口规范。returnresponse.data},error{if(error.response?.status401){localStorage.removeItem(access)// 可以在这里触发路由跳转或统一提示。}returnPromise.reject(error)})exportdefaultrequest拦截器应保持职责单一不要在其中放置复杂业务逻辑。给请求额外添加自定义请求头可能触发 CORS 预检请求后端需要允许对应的请求方法和请求头。四、Promise、async 与 awaitPromise 表示一个未来才会完成的异步操作有pending、fulfilled和rejected三种状态。状态一旦从 pending 变为成功或失败就不会再次改变。consttasknewPromise((resolve,reject){setTimeout((){constsuccesstrueif(success)resolve(任务完成)elsereject(newError(任务失败))},1000)})task.then(valueconsole.log(value)).catch(errorconsole.error(error)).finally(()console.log(任务结束))Promise 的回调不会阻塞浏览器主线程。then处理成功值catch处理异常在链式调用中每个then的返回值都会成为下一个then的输入。async函数总是返回 Promiseawait会等待右侧 Promise 的结果只能在 async 函数中使用现代环境也支持顶层 await 的特定场景。asyncfunctionloadUser(){try{constresponseawaitrequest.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 4Vue 3 使用 Vue Router 4。安装后通过createRouter和createWebHistory创建路由实例再通过app.use(router)注册。npminstallvue-router4// src/router/index.jsimport{createRouter,createWebHistory}fromvue-routerimportHomeViewfrom/views/HomeView.vueimportLoginViewfrom/views/LoginView.vueconstroutes[{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}fromvueimportAppfrom./App.vueimportrouterfrom./routercreateApp(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 clickgoAbout查看详情/button p当前路径{{ route.path }}/p /templatepush会新增历史记录replace会替换当前记录back和go(-1)可以返回。路径参数使用route.params获取查询参数使用route.query获取。六、Vue Router 4 路由守卫Vue Router 4 的全局守卫可以直接返回结果不再强制使用 Vue Router 3 的next回调。守卫适合做登录状态检查但接口权限仍必须由后端验证。router.beforeEach(to{consttokenlocalStorage.getItem(access)constrequiresAuthto.matched.some(recordrecord.meta.requiresAuth)if(requiresAuth!token){return{name:login,query:{redirect:to.fullPath}}}if(to.namelogintoken){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}fromvueimport{defineStore}frompiniaexportconstuseUserStoredefineStore(user,(){constusernameref(张三)constageref(19)consthobbiesref([阅读,音乐])consthobbyCountcomputed(()hobbies.value.length)functionincreaseAge(){age.value}return{username,age,hobbies,hobbyCount,increaseAge}})// main.jsimport{createApp}fromvueimport{createPinia}frompiniaimportAppfrom./App.vuecreateApp(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 clickuserStore.increaseAge增加年龄/button /template只有跨组件或跨页面共享的数据才值得放入 Pinia。局部表单输入和临时显示状态放在组件内部避免 store 变成无边界的全局变量集合。八、Element PlusElement Plus 是 Vue 3 对应的桌面端组件库Vue 2 应使用 Element UI。安装后在入口文件注册可以使用按钮、表格、表单、弹窗等组件。npminstallelement-plus// main.jsimport{createApp}fromvueimportElementPlusfromelement-plusimportelement-plus/dist/index.cssimportAppfrom./App.vuecreateApp(App).use(ElementPlus).mount(#app)template el-button typeprimary clickshowMessage保存/el-button /template script setup import { ElMessage } from element-plus function showMessage() { ElMessage.success(保存成功) } /script组件库可以提高开发效率但仍应统一表单校验、错误提示和主题规范。大型项目可根据文档使用按需引入减少最终打包体积。九、总结toRefs适合解构整个响应式对象toRef适合保持单个属性的响应式连接。axios 实例和拦截器可以统一基础地址、token、超时和错误处理async/await让异步流程更清晰。Vue Router 4 使用createRouter和组合式 API路由守卫可以控制前端导航但不能替代后端权限。Pinia 是 Vue 3 推荐的状态管理库按业务拆分 store避免把所有状态都放到全局。Element Plus 与 Vue 3 配套Element UI 与 Vue 2 配套选择组件库时要先确认项目版本。