在Vue3工程中,响应式解构、Axios请求层、路由守卫和Pinia状态管理经常组合出现。它们分别对应数据响应性、HTTP请求封装、前端导航权限和跨组件状态拆分。下面按这些模块整理可落地的代码逻辑、参数含义和常见错误点,示例可直接对照项目结构调整。
一、reactive解构与toRefs、toRef
reactive()返回响应式代理对象,直接解构代理对象得到的是普通值,响应式连接会丢失。toRefs()会为对象每个属性创建ref并保持双向同步,toRef()用于保持单个属性的引用。需要区分:ref(person.name)只是用当前值创建新ref,之后与person.name没有自动关联;toRef(person,'name')才是对原对象属性的引用。组合式函数返回响应式对象时,常用toRefs让调用方安全解构。- import { reactive, toRefs, toRef } from 'vue'
- const person = reactive({ name: 'lqz', age: 19 })
- const { name: plainName } = person
- const { name, age } = toRefs(person)
- age.value++
- console.log(person.age) // 20
- const personName = toRef(person, 'name')
- personName.value = '新名字'
- console.log(person.name)
复制代码 组件中可按下面方式使用,模板读取name和age,事件中修改age.value。- <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。安装命令为npm install axios。请求电影列表时,可用loading和error管理界面状态,并根据接口返回的code判断业务失败。- <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/')
- 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>
复制代码 响应式数组使用reactive([])时,可用splice原地更新;也可以改用ref([])重新赋值。若使用reactive({results:[]}),可用Object.assign(state,data)更新对象多个属性,但不要把数组当普通对象使用后还期待所有场景都清晰可读。
常用配置可分为请求地址、请求数据和请求行为。params用于拼接到URL的查询参数,data用于POST、PUT、PATCH等请求体;timeout可防止请求无限等待,baseURL适合统一管理后端API前缀,headers可携带认证信息。- axios({
- baseURL: 'http://127.0.0.1:8000/api',
- url: '/films/',
- method: 'get',
- params: { page: 1, size: 10 },
- headers: { Authorization: 'Bearer token' },
- timeout: 10000
- })
- axios.post('/login/', {
- username: 'lqz',
- password: 'secret'
- })
复制代码
三、Axios实例与拦截器封装
项目中通常创建一个axios实例,把基础地址、超时、认证请求头和错误处理集中管理。请求拦截器在请求发出前执行,响应拦截器在then之前执行。拦截器应保持职责单一,不要在其中放置复杂业务逻辑。是否在响应拦截器中直接返回response.data,取决于项目接口规范。- import axios from 'axios'
- const request = axios.create({
- baseURL: 'http://127.0.0.1:8000/api/v1',
- timeout: 10000
- })
- request.interceptors.request.use(config => {
- const token = localStorage.getItem('access')
- if (token) {
- config.headers.Authorization = `Bearer ${token}`
- }
- return config
- }, error => Promise.reject(error))
- request.interceptors.response.use(response => {
- return response.data
- }, error => {
- if (error.response?.status === 401) {
- localStorage.removeItem('access')
- }
- return Promise.reject(error)
- })
- export default request
复制代码 401时清理token后,可以按项目需要触发路由跳转或统一提示。给请求额外添加自定义请求头可能触发CORS预检请求,后端需要允许对应的请求方法和请求头,否则会出现浏览器侧跨域错误。
四、Promise、async与await
Promise表示未来才会完成的异步操作,有pending、fulfilled和rejected三种状态,状态一旦从pending变为成功或失败就不会再次改变。then处理成功值,catch处理异常,finally做收尾;链式调用中每个then的返回值会成为下一个then的输入。Promise回调不会阻塞浏览器主线程。- const task = new Promise((resolve, reject) => {
- setTimeout(() => {
- const success = true
- if (success) resolve('任务完成')
- else reject(new Error('任务失败'))
- }, 1000)
- })
- task
- .then(value => console.log(value))
- .catch(error => console.error(error))
- .finally(() => console.log('任务结束'))
复制代码 async函数总是返回Promise,await会等待右侧Promise的结果,只能在async函数中使用;现代环境在特定场景支持顶层await。多个互不依赖的请求不必串行等待,可用Promise.all并行发起;只有后一个请求依赖前一个结果时,才按顺序写多个await。- async function loadUser() {
- try {
- const response = await request.get('/user/')
- console.log(response)
- return response
- } catch (error) {
- console.error('请求失败', error)
- throw error
- }
- }
- const [user, settings] = await Promise.all([
- request.get('/user/'),
- request.get('/settings/')
- ])
复制代码
五、Vue Router 4动态参数与导航
Vue3使用Vue Router 4,安装命令为npm install vue-router@4。通过createRouter和createWebHistory创建路由实例,再通过app.use(router)注册。组件模板中使用router-link声明式导航,页面组件显示在router-view中;JavaScript中使用useRouter()获取路由实例,使用useRoute()获取当前路由对象。- import { createRouter, createWebHistory } from 'vue-router'
- import HomeView from '@/views/HomeView.vue'
- import LoginView from '@/views/LoginView.vue'
- const routes = [
- { path: '/', name: 'home', component: HomeView },
- { path: '/login', name: 'login', component: LoginView },
- {
- path: '/about/:id',
- name: 'about',
- component: () => import('@/views/AboutView.vue')
- }
- ]
- export default createRouter({
- history: createWebHistory(import.meta.env.BASE_URL),
- routes
- })
复制代码 入口文件注册路由:- import { createApp } from 'vue'
- import App from './App.vue'
- import router from './router'
- createApp(App).use(router).mount('#app')
复制代码 组件中跳转并读取参数:- <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回调。守卫适合做登录状态检查,但接口权限仍必须由后端验证。路由可通过meta.requiresAuth标记是否需要登录。- router.beforeEach(to => {
- const token = localStorage.getItem('access')
- const requiresAuth = 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' }
- }
- return true
- })
复制代码- {
- path: '/admin',
- component: () => import('@/views/AdminView.vue'),
- meta: { requiresAuth: true }
- }
复制代码 要避免登录页本身被守卫重定向造成死循环。登录成功后读取redirect时,应确认它是本站路径,避免开放重定向。前端守卫只控制导航,不能替代后端接口鉴权。
七、Pinia状态管理
Pinia是Vue3官方推荐的状态管理库,相比Vuex,API更简洁,不需要强制区分mutations和actions,支持更好的TypeScript推导,也可以按store拆分业务模块。安装命令为npm install pinia。Setup Store写法更贴合组合式API,使用ref、computed和函数组织状态与操作。- import { computed, ref } from 'vue'
- import { defineStore } from 'pinia'
- export const useUserStore = defineStore('user', () => {
- const username = ref('张三')
- const age = ref(19)
- const hobbies = ref(['阅读', '音乐'])
- const hobbyCount = computed(() => hobbies.value.length)
- function increaseAge() {
- age.value++
- }
- return { username, age, hobbies, hobbyCount, increaseAge }
- })
复制代码 入口文件注册Pinia:- import { createApp } from 'vue'
- import { createPinia } from 'pinia'
- import App from './App.vue'
- createApp(App).use(createPinia()).mount('#app')
复制代码 组件中调用store:- <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是Vue3对应的桌面端组件库;Vue2应使用Element UI。安装后在入口文件注册,可使用按钮、表格、表单、弹窗等组件。- import { createApp } from 'vue'
- import ElementPlus from 'element-plus'
- import 'element-plus/dist/index.css'
- import App from './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>
复制代码 组件库可以提高开发效率,但仍应统一表单校验、错误提示和主题规范。大型项目可根据文档使用按需引入,减少最终打包体积。选择组件库时要先确认项目版本:Element Plus对应Vue3,Element UI对应Vue2。
九、落地要点
toRefs适合解构整个响应式对象,toRef适合保持单个属性的响应式连接。Axios实例和拦截器可统一基础地址、token、超时和错误处理;async/await让异步流程更清晰,Promise.all用于并行请求。Vue Router 4使用createRouter和组合式API,路由守卫控制前端导航,但不能替代后端权限。Pinia按业务拆分store,避免把所有状态都放到全局。Element Plus与Vue3配套,版本选择要先确认项目技术栈。 |