这是一个非常重要且常见的需求。在 Vue 3 中,我们通常不会真正地将异步请求"变成"同步(因为这会阻塞浏览器),而是通过一些模式让代码具有“同步”的书写和阅读风格,从而避免“回调地狱”。
核心方法是使用 async
/await
语法,并结合 Vue 3 的响应式 API 来管理状态。
核心思路
-
创建响应式状态:使用
ref
或reactive
来存储请求的数据(data)
、加载状态(loading)
和错误信息(error)
。 -
封装异步函数:创建一个
async
函数,内部使用try...catch
来处理请求。 -
使用
await
:在函数内部await
异步请求,这样后面的代码就会“等待”请求完成后再执行,实现了代码逻辑上的“同步”。 -
暴露状态和函数:将响应式状态和封装的函数暴露给组件使用。
封装示例:一个通用的异步请求 Hook/Composable
我们将创建一个名为 useRequest
的组合式函数,这是 Vue 3 组合式 API 推荐的封装方式。
第一步:创建 useRequest
函数
javascript
// composables/useRequest.js (或 useRequest.ts) import { ref } from 'vue' export function useRequest(apiFn) { // 1. 创建响应式状态 const data = ref(null) // 存储请求成功的数据 const loading = ref(false) // 加载状态 const error = ref(null) // 存储请求失败的错误信息 // 2. 封装异步请求函数 const execute = async (...params) => { loading.value = true error.value = null data.value = null // 每次请求前重置数据 try { // 使用 await "等待"请求完成,代码在此处暂停,直到请求有结果 const response = await apiFn(...params) data.value = response.data // 假设后端返回的数据在 response.data 中 return response // 如果需要,可以返回完整的响应 } catch (err) { error.value = err.message || '请求失败' throw err // 将错误继续抛出,以便在调用处可以捕获 } finally { loading.value = false // 无论成功失败,最后都关闭 loading } } // 3. 返回状态和执行函数,供组件使用 return { data, loading, error, execute } }
第二步:封装具体的 API 请求
通常,我们会使用 axios
或 fetch
等库。这里以 axios
为例。
javascript
// api/userApi.js import axios from 'axios' // 封装获取用户信息的 API export const getUserInfo = (userId) => { return axios.get(`/api/user/${userId}`) } // 封装提交表单的 API export const postUserData = (userData) => { return axios.post('/api/user', userData) }
第三步:在 Vue 组件中使用
现在我们可以在组件中像使用“同步”代码一样调用异步请求了。
vue
<!-- UserProfile.vue --> <template> <div> <!-- 加载状态 --> <div v-if="loading">Loading...</div> <!-- 错误状态 --> <div v-else-if="error" class="error">{{ error }}</div> <!-- 成功状态 --> <div v-else-if="data"> <h1>用户信息</h1> <p>姓名: {{ data.name }}</p> <p>邮箱: {{ data.email }}</p> </div> <button @click="fetchUser(123)" :disabled="loading"> {{ loading ? '请求中...' : '获取用户信息' }} </button> </div> </template> <script setup> import { useRequest } from '@/composables/useRequest' import { getUserInfo } from '@/api/userApi' // 使用 useRequest Hook,传入具体的 API 函数 const { data, loading, error, execute: fetchUser } = useRequest(getUserInfo) // 你也可以在组件挂载时自动调用 // fetchUser(123) // 传入参数 123 作为 userId // 或者响应某个事件,如按钮点击 const handleClick = async () => { try { // 这里的代码会“等待”请求完成,仿佛在同步执行 await fetchUser(123) // 请求成功后才执行这里的代码 console.log('用户数据获取成功:', data.value) } catch (err) { // 错误已经在 useRequest 中处理了,这里可以做一些额外操作 console.error('在组件中捕获到错误:', err) } } </script>
更进一步的封装:直接请求并返回 Promise
如果你不需要管理 loading 和 error 状态,只是想简单地调用一个函数并等待结果,可以更简单地封装:
javascript
// utils/request.js import axios from 'axios' export const request = async (config) => { try { const response = await axios(config) return response.data // 直接返回数据 } catch (error) { // 可以在这里统一处理错误,例如弹出通知 console.error('请求错误:', error) throw error // 重新抛出错误,让调用方可以捕获 } }
在组件中使用:
vue
<script setup> import { request } from '@/utils/request' import { ref } from 'vue' const userData = ref(null) const fetchUser = async () => { try { // 代码像同步一样简洁 userData.value = await request({ method: 'get', url: '/api/user/123' }) console.log('请求成功,数据已更新') } catch (error) { console.error('请求失败', error) } } </script>
总结
方法 | 优点 | 适用场景 |
---|---|---|
useRequest Hook | 功能强大,自带状态管理(loading, error, data) | 需要 UI 响应请求状态的场景(如显示加载动画、错误提示) |
简单 request 函数 | 极其简洁,只需关注数据本身 | 简单的请求,不需要管理加载和错误状态 |
关键记住:Vue 3 中并没有真正将异步变同步的魔法,我们是通过 async/await
语法让异步代码的书写和阅读顺序像同步一样直观,同时利用响应式系统来优雅地更新UI状态。这种模式极大地改善了开发体验和代码可维护性。