查看: 264|回复: 0

Vue3响应式解构Axios封装路由守卫Pinia实践

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
在Vue3工程中,响应式解构、Axios请求层、路由守卫和Pinia状态管理经常组合出现。它们分别对应数据响应性、HTTP请求封装、前端导航权限和跨组件状态拆分。下面按这些模块整理可落地的代码逻辑、参数含义和常见错误点,示例可直接对照项目结构调整。

一、reactive解构与toRefs、toRef
reactive()返回响应式代理对象,直接解构代理对象得到的是普通值,响应式连接会丢失。toRefs()会为对象每个属性创建ref并保持双向同步,toRef()用于保持单个属性的引用。需要区分:ref(person.name)只是用当前值创建新ref,之后与person.name没有自动关联;toRef(person,'name')才是对原对象属性的引用。组合式函数返回响应式对象时,常用toRefs让调用方安全解构。
  1. import { reactive, toRefs, toRef } from 'vue'
  2. const person = reactive({ name: 'lqz', age: 19 })
  3. const { name: plainName } = person
  4. const { name, age } = toRefs(person)
  5. age.value++
  6. console.log(person.age) // 20
  7. const personName = toRef(person, 'name')
  8. personName.value = '新名字'
  9. console.log(person.name)
复制代码
组件中可按下面方式使用,模板读取name和age,事件中修改age.value。
  1. <script setup>
  2. import { reactive, toRefs } from 'vue'
  3. const person = reactive({ name: 'lqz', age: 19 })
  4. const { name, age } = toRefs(person)
  5. function increaseAge() {
  6.   age.value++
  7. }
  8. </script>
  9. <template>
  10.   <p>{{ name }},{{ age }} 岁</p>
  11.   <button @click='increaseAge'>增加年龄</button>
  12. </template>
复制代码

二、Axios基本使用与配置项
axios是基于Promise的HTTP客户端,响应对象通常包含data、status、headers等信息,后端业务数据一般位于response.data。安装命令为npm install axios。请求电影列表时,可用loading和error管理界面状态,并根据接口返回的code判断业务失败。
  1. <script setup>
  2. import { reactive, ref, onMounted } from 'vue'
  3. import axios from 'axios'
  4. const films = reactive([])
  5. const loading = ref(false)
  6. const error = ref('')
  7. async function loadFilms() {
  8.   loading.value = true
  9.   error.value = ''
  10.   try {
  11.     const { data } = await axios.get('http://127.0.0.1:8000/films/films/')
  12.     if (data.code !== 0) throw new Error(data.msg || '接口返回失败')
  13.     films.splice(0, films.length, ...data.results)
  14.   } catch (err) {
  15.     console.error(err)
  16.     error.value = '电影数据加载失败'
  17.   } finally {
  18.     loading.value = false
  19.   }
  20. }
  21. onMounted(loadFilms)
  22. </script>
复制代码
响应式数组使用reactive([])时,可用splice原地更新;也可以改用ref([])重新赋值。若使用reactive({results:[]}),可用Object.assign(state,data)更新对象多个属性,但不要把数组当普通对象使用后还期待所有场景都清晰可读。

常用配置可分为请求地址、请求数据和请求行为。params用于拼接到URL的查询参数,data用于POST、PUT、PATCH等请求体;timeout可防止请求无限等待,baseURL适合统一管理后端API前缀,headers可携带认证信息。
  1. axios({
  2.   baseURL: 'http://127.0.0.1:8000/api',
  3.   url: '/films/',
  4.   method: 'get',
  5.   params: { page: 1, size: 10 },
  6.   headers: { Authorization: 'Bearer token' },
  7.   timeout: 10000
  8. })
  9. axios.post('/login/', {
  10.   username: 'lqz',
  11.   password: 'secret'
  12. })
复制代码

三、Axios实例与拦截器封装
项目中通常创建一个axios实例,把基础地址、超时、认证请求头和错误处理集中管理。请求拦截器在请求发出前执行,响应拦截器在then之前执行。拦截器应保持职责单一,不要在其中放置复杂业务逻辑。是否在响应拦截器中直接返回response.data,取决于项目接口规范。
  1. import axios from 'axios'
  2. const request = axios.create({
  3.   baseURL: 'http://127.0.0.1:8000/api/v1',
  4.   timeout: 10000
  5. })
  6. request.interceptors.request.use(config => {
  7.   const token = localStorage.getItem('access')
  8.   if (token) {
  9.     config.headers.Authorization = `Bearer ${token}`
  10.   }
  11.   return config
  12. }, error => Promise.reject(error))
  13. request.interceptors.response.use(response => {
  14.   return response.data
  15. }, error => {
  16.   if (error.response?.status === 401) {
  17.     localStorage.removeItem('access')
  18.   }
  19.   return Promise.reject(error)
  20. })
  21. export default request
复制代码
401时清理token后,可以按项目需要触发路由跳转或统一提示。给请求额外添加自定义请求头可能触发CORS预检请求,后端需要允许对应的请求方法和请求头,否则会出现浏览器侧跨域错误。

四、Promise、async与await
Promise表示未来才会完成的异步操作,有pending、fulfilled和rejected三种状态,状态一旦从pending变为成功或失败就不会再次改变。then处理成功值,catch处理异常,finally做收尾;链式调用中每个then的返回值会成为下一个then的输入。Promise回调不会阻塞浏览器主线程。
  1. const task = new Promise((resolve, reject) => {
  2.   setTimeout(() => {
  3.     const success = true
  4.     if (success) resolve('任务完成')
  5.     else reject(new Error('任务失败'))
  6.   }, 1000)
  7. })
  8. task
  9.   .then(value => console.log(value))
  10.   .catch(error => console.error(error))
  11.   .finally(() => console.log('任务结束'))
复制代码
async函数总是返回Promise,await会等待右侧Promise的结果,只能在async函数中使用;现代环境在特定场景支持顶层await。多个互不依赖的请求不必串行等待,可用Promise.all并行发起;只有后一个请求依赖前一个结果时,才按顺序写多个await。
  1. async function loadUser() {
  2.   try {
  3.     const response = await request.get('/user/')
  4.     console.log(response)
  5.     return response
  6.   } catch (error) {
  7.     console.error('请求失败', error)
  8.     throw error
  9.   }
  10. }
  11. const [user, settings] = await Promise.all([
  12.   request.get('/user/'),
  13.   request.get('/settings/')
  14. ])
复制代码

五、Vue Router 4动态参数与导航
Vue3使用Vue Router 4,安装命令为npm install vue-router@4。通过createRouter和createWebHistory创建路由实例,再通过app.use(router)注册。组件模板中使用router-link声明式导航,页面组件显示在router-view中;JavaScript中使用useRouter()获取路由实例,使用useRoute()获取当前路由对象。
  1. import { createRouter, createWebHistory } from 'vue-router'
  2. import HomeView from '@/views/HomeView.vue'
  3. import LoginView from '@/views/LoginView.vue'
  4. const routes = [
  5.   { path: '/', name: 'home', component: HomeView },
  6.   { path: '/login', name: 'login', component: LoginView },
  7.   {
  8.     path: '/about/:id',
  9.     name: 'about',
  10.     component: () => import('@/views/AboutView.vue')
  11.   }
  12. ]
  13. export default createRouter({
  14.   history: createWebHistory(import.meta.env.BASE_URL),
  15.   routes
  16. })
复制代码
入口文件注册路由:
  1. import { createApp } from 'vue'
  2. import App from './App.vue'
  3. import router from './router'
  4. createApp(App).use(router).mount('#app')
复制代码
组件中跳转并读取参数:
  1. <script setup>
  2. import { useRouter, useRoute } from 'vue-router'
  3. const router = useRouter()
  4. const route = useRoute()
  5. function goAbout() {
  6.   router.push({
  7.     name: 'about',
  8.     params: { id: 10 },
  9.     query: { from: 'home' }
  10.   })
  11. }
  12. </script>
  13. <template>
  14.   <router-link to='/'>首页</router-link>
  15.   <button @click='goAbout'>查看详情</button>
  16.   <p>当前路径:{{ route.path }}</p>
  17. </template>
复制代码
push会新增历史记录,replace会替换当前记录,back和go(-1)可以返回。路径参数使用route.params获取,查询参数使用route.query获取。

六、Vue Router 4路由守卫
Vue Router 4的全局守卫可以直接返回结果,不再强制使用Vue Router 3的next回调。守卫适合做登录状态检查,但接口权限仍必须由后端验证。路由可通过meta.requiresAuth标记是否需要登录。
  1. router.beforeEach(to => {
  2.   const token = localStorage.getItem('access')
  3.   const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
  4.   if (requiresAuth && !token) {
  5.     return {
  6.       name: 'login',
  7.       query: { redirect: to.fullPath }
  8.     }
  9.   }
  10.   if (to.name === 'login' && token) {
  11.     return { name: 'home' }
  12.   }
  13.   return true
  14. })
复制代码
  1. {
  2.   path: '/admin',
  3.   component: () => import('@/views/AdminView.vue'),
  4.   meta: { requiresAuth: true }
  5. }
复制代码
要避免登录页本身被守卫重定向造成死循环。登录成功后读取redirect时,应确认它是本站路径,避免开放重定向。前端守卫只控制导航,不能替代后端接口鉴权。

七、Pinia状态管理
Pinia是Vue3官方推荐的状态管理库,相比Vuex,API更简洁,不需要强制区分mutations和actions,支持更好的TypeScript推导,也可以按store拆分业务模块。安装命令为npm install pinia。Setup Store写法更贴合组合式API,使用ref、computed和函数组织状态与操作。
  1. import { computed, ref } from 'vue'
  2. import { defineStore } from 'pinia'
  3. export const useUserStore = defineStore('user', () => {
  4.   const username = ref('张三')
  5.   const age = ref(19)
  6.   const hobbies = ref(['阅读', '音乐'])
  7.   const hobbyCount = computed(() => hobbies.value.length)
  8.   function increaseAge() {
  9.     age.value++
  10.   }
  11.   return { username, age, hobbies, hobbyCount, increaseAge }
  12. })
复制代码
入口文件注册Pinia:
  1. import { createApp } from 'vue'
  2. import { createPinia } from 'pinia'
  3. import App from './App.vue'
  4. createApp(App).use(createPinia()).mount('#app')
复制代码
组件中调用store:
  1. <script setup>
  2. import { useUserStore } from '@/stores/user'
  3. const userStore = useUserStore()
  4. </script>
  5. <template>
  6.   <p>{{ userStore.username }}:{{ userStore.age }} 岁</p>
  7.   <p>爱好数量:{{ userStore.hobbyCount }}</p>
  8.   <button @click='userStore.increaseAge'>增加年龄</button>
  9. </template>
复制代码
只有跨组件或跨页面共享的数据才值得放入Pinia。局部表单输入和临时显示状态放在组件内部,避免store变成无边界的全局变量集合。

八、Element Plus与版本配套
Element Plus是Vue3对应的桌面端组件库;Vue2应使用Element UI。安装后在入口文件注册,可使用按钮、表格、表单、弹窗等组件。
  1. import { createApp } from 'vue'
  2. import ElementPlus from 'element-plus'
  3. import 'element-plus/dist/index.css'
  4. import App from './App.vue'
  5. createApp(App).use(ElementPlus).mount('#app')
复制代码
  1. <template>
  2.   <el-button type='primary' @click='showMessage'>保存</el-button>
  3. </template>
  4. <script setup>
  5. import { ElMessage } from 'element-plus'
  6. function showMessage() {
  7.   ElMessage.success('保存成功')
  8. }
  9. </script>
复制代码
组件库可以提高开发效率,但仍应统一表单校验、错误提示和主题规范。大型项目可根据文档使用按需引入,减少最终打包体积。选择组件库时要先确认项目版本:Element Plus对应Vue3,Element UI对应Vue2。

九、落地要点
toRefs适合解构整个响应式对象,toRef适合保持单个属性的响应式连接。Axios实例和拦截器可统一基础地址、token、超时和错误处理;async/await让异步流程更清晰,Promise.all用于并行请求。Vue Router 4使用createRouter和组合式API,路由守卫控制前端导航,但不能替代后端权限。Pinia按业务拆分store,避免把所有状态都放到全局。Element Plus与Vue3配套,版本选择要先确认项目技术栈。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-25 14:38 , Processed in 0.028712 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部