Vue组件内封装网络请求提升代码复用性
为什么要在 Vue 组件内封装网络请求
在前端开发中,网络请求是非常常见的操作。无论是获取数据展示在页面上,还是将用户输入的数据提交到服务器,都离不开网络请求。然而,如果在每个需要进行网络请求的地方都直接编写请求代码,会导致代码的大量重复,难以维护和管理。例如,假设我们有一个博客应用,在文章列表页面、文章详情页面、评论列表页面等多个地方都需要从服务器获取数据。如果每个页面都直接编写获取数据的网络请求代码,当服务器的 API 发生变化时,就需要在多个地方进行修改,这无疑增加了出错的概率和维护成本。
通过在 Vue 组件内封装网络请求,可以将网络请求相关的逻辑抽取出来,实现代码的复用。这样,当网络请求的逻辑发生变化时,只需要在封装的地方进行修改,而不需要在每个使用到该请求的组件中逐个修改。同时,封装后的网络请求代码更加简洁和清晰,便于理解和维护。
准备工作
在开始封装网络请求之前,我们需要选择一个合适的网络请求库。在 Vue 项目中,Axios 是一个非常受欢迎的网络请求库,它具有简洁的 API、支持 Promise、能够拦截请求和响应等优点。因此,我们将使用 Axios 来进行网络请求的封装。
首先,确保你的 Vue 项目已经安装了 Axios。如果没有安装,可以通过以下命令进行安装:
npm install axios --save
安装完成后,在 main.js 文件中引入 Axios 并将其挂载到 Vue 实例上,这样在组件中就可以通过 this.$axios
来使用 Axios 进行网络请求了。
import Vue from 'vue'
import axios from 'axios'
Vue.prototype.$axios = axios
简单封装网络请求
我们先从一个简单的封装开始,以获取文章列表的网络请求为例。假设服务器提供的 API 地址为 /api/articles
,请求方法为 GET。
首先,在 src
目录下创建一个 api
文件夹,用于存放所有与网络请求相关的代码。在 api
文件夹中创建一个 article.js
文件,用于封装与文章相关的网络请求。
// src/api/article.js
import axios from 'axios'
// 封装获取文章列表的请求
export function getArticleList() {
return axios.get('/api/articles')
}
在上述代码中,我们通过 axios.get
方法发送一个 GET 请求到 /api/articles
地址,并返回一个 Promise 对象。这样,在 Vue 组件中就可以通过调用 getArticleList
函数来获取文章列表数据了。
接下来,在 Vue 组件中使用这个封装好的网络请求。假设我们有一个 ArticleList.vue
组件,代码如下:
<template>
<div>
<ul>
<li v-for="article in articleList" :key="article.id">{{ article.title }}</li>
</ul>
</div>
</template>
<script>
import { getArticleList } from '@/api/article'
export default {
data() {
return {
articleList: []
}
},
created() {
this.fetchArticleList()
},
methods: {
async fetchArticleList() {
try {
const response = await getArticleList()
this.articleList = response.data
} catch (error) {
console.error('获取文章列表失败', error)
}
}
}
}
</script>
在上述代码中,我们在 ArticleList.vue
组件的 created
钩子函数中调用 fetchArticleList
方法来获取文章列表数据。fetchArticleList
方法通过 await
等待 getArticleList
函数返回的 Promise 对象被 resolve,然后将响应数据赋值给 articleList
数据属性,最后在模板中进行展示。
带参数的网络请求封装
在实际开发中,很多网络请求需要携带参数。例如,获取特定分类的文章列表,就需要在请求 URL 中传递分类参数。假设服务器提供的 API 地址为 /api/articles?category=tech
,其中 category
是分类参数。
我们修改 article.js
文件中的 getArticleList
函数,使其支持传递参数:
// src/api/article.js
import axios from 'axios'
// 封装获取文章列表的请求,支持传递参数
export function getArticleList(params) {
return axios.get('/api/articles', { params })
}
在上述代码中,我们通过 axios.get
方法的第二个参数 { params }
来传递请求参数。
然后,在 ArticleList.vue
组件中,我们可以根据需要传递不同的参数来获取不同分类的文章列表:
<template>
<div>
<button @click="fetchArticleList('tech')">获取技术类文章列表</button>
<button @click="fetchArticleList('life')">获取生活类文章列表</button>
<ul>
<li v-for="article in articleList" :key="article.id">{{ article.title }}</li>
</ul>
</div>
</template>
<script>
import { getArticleList } from '@/api/article'
export default {
data() {
return {
articleList: []
}
},
methods: {
async fetchArticleList(category) {
try {
const response = await getArticleList({ category })
this.articleList = response.data
} catch (error) {
console.error('获取文章列表失败', error)
}
}
}
}
</script>
在上述代码中,我们通过点击按钮来调用 fetchArticleList
方法,并传递不同的分类参数,从而获取不同分类的文章列表数据。
封装 POST 请求
除了 GET 请求,POST 请求也是非常常用的。例如,用户提交评论时,就需要使用 POST 请求将评论数据发送到服务器。假设服务器提供的 API 地址为 /api/comments
,请求方法为 POST,请求数据格式为 JSON。
在 api
文件夹中创建一个 comment.js
文件,用于封装与评论相关的网络请求:
// src/api/comment.js
import axios from 'axios'
// 封装提交评论的请求
export function postComment(data) {
return axios.post('/api/comments', data)
}
在上述代码中,我们通过 axios.post
方法发送一个 POST 请求到 /api/comments
地址,并将评论数据 data
作为请求体发送出去。
接下来,在一个用于提交评论的 Vue 组件 CommentForm.vue
中使用这个封装好的网络请求:
<template>
<div>
<form @submit.prevent="submitComment">
<label for="content">评论内容:</label>
<input type="text" id="content" v-model="commentContent" />
<button type="submit">提交评论</button>
</form>
</div>
</template>
<script>
import { postComment } from '@/api/comment'
export default {
data() {
return {
commentContent: ''
}
},
methods: {
async submitComment() {
const commentData = {
content: this.commentContent
}
try {
const response = await postComment(commentData)
console.log('评论提交成功', response.data)
this.commentContent = ''
} catch (error) {
console.error('评论提交失败', error)
}
}
}
}
</script>
在上述代码中,当用户点击提交按钮时,submitComment
方法会被调用。该方法首先构造评论数据 commentData
,然后通过 await
等待 postComment
函数返回的 Promise 对象被 resolve。如果评论提交成功,会清空输入框中的内容;如果失败,则在控制台打印错误信息。
处理响应拦截
在实际开发中,服务器返回的响应数据可能需要进行一些统一的处理,例如检查响应状态码、处理错误信息等。Axios 提供了响应拦截器的功能,可以在响应被 then
或 catch
处理前对其进行拦截和处理。
在 main.js
文件中添加响应拦截器:
import Vue from 'vue'
import axios from 'axios'
Vue.prototype.$axios = axios
// 添加响应拦截器
axios.interceptors.response.use(
response => {
// 这里可以对响应数据进行统一处理,例如检查状态码
if (response.status === 200) {
return response.data
} else {
throw new Error('响应状态码错误')
}
},
error => {
// 这里可以对错误进行统一处理
console.error('网络请求错误', error)
return Promise.reject(error)
}
)
在上述代码中,我们通过 axios.interceptors.response.use
方法添加了一个响应拦截器。在成功的回调函数中,我们检查响应状态码是否为 200,如果是,则直接返回响应数据 response.data
;否则,抛出一个错误。在错误的回调函数中,我们在控制台打印错误信息,并将错误通过 Promise.reject
继续传递,以便在组件中进行捕获和处理。
这样,在组件中使用封装好的网络请求时,就不需要每次都检查响应状态码了,代码更加简洁:
<template>
<div>
<ul>
<li v-for="article in articleList" :key="article.id">{{ article.title }}</li>
</ul>
</div>
</template>
<script>
import { getArticleList } from '@/api/article'
export default {
data() {
return {
articleList: []
}
},
created() {
this.fetchArticleList()
},
methods: {
async fetchArticleList() {
try {
const data = await getArticleList()
this.articleList = data
} catch (error) {
console.error('获取文章列表失败', error)
}
}
}
}
</script>
在上述代码中,await getArticleList()
直接返回的就是经过响应拦截器处理后的响应数据 data
,如果响应状态码不是 200,会直接进入 catch
块处理错误。
处理请求拦截
除了响应拦截,Axios 还提供了请求拦截器的功能。请求拦截器可以在请求被发送前对其进行拦截和处理,例如添加请求头、对请求数据进行格式化等。
假设我们的服务器需要在请求头中携带一个认证令牌 token
,我们可以通过请求拦截器来添加这个 token
:
import Vue from 'vue'
import axios from 'axios'
Vue.prototype.$axios = axios
// 添加请求拦截器
axios.interceptors.request.use(
config => {
// 假设这里从本地存储中获取 token
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
// 添加响应拦截器
axios.interceptors.response.use(
response => {
if (response.status === 200) {
return response.data
} else {
throw new Error('响应状态码错误')
}
},
error => {
console.error('网络请求错误', error)
return Promise.reject(error)
}
)
在上述代码中,我们通过 axios.interceptors.request.use
方法添加了一个请求拦截器。在成功的回调函数中,我们从本地存储中获取 token
,并将其添加到请求头的 Authorization
字段中。这样,每个通过 Axios 发送的请求都会带上这个 token
。
封装多个请求并发处理
在一些场景下,我们可能需要同时发起多个网络请求,并在所有请求都完成后进行一些操作。例如,在一个用户详情页面,我们可能需要同时获取用户的基本信息、文章列表和评论列表。Axios 提供了 axios.all
和 axios.spread
方法来处理多个请求并发的情况。
在 api
文件夹中创建一个 user.js
文件,用于封装与用户相关的网络请求:
// src/api/user.js
import axios from 'axios'
// 封装获取用户基本信息的请求
export function getUserInfo() {
return axios.get('/api/user/info')
}
// 封装获取用户文章列表的请求
export function getUserArticleList() {
return axios.get('/api/user/articles')
}
// 封装获取用户评论列表的请求
export function getUserCommentList() {
return axios.get('/api/user/comments')
}
在一个 UserDetail.vue
组件中,我们可以同时发起这三个请求,并在所有请求都完成后进行数据处理:
<template>
<div>
<h2>{{ userInfo.name }}</h2>
<h3>文章列表</h3>
<ul>
<li v-for="article in userArticleList" :key="article.id">{{ article.title }}</li>
</ul>
<h3>评论列表</h3>
<ul>
<li v-for="comment in userCommentList" :key="comment.id">{{ comment.content }}</li>
</ul>
</div>
</template>
<script>
import { getUserInfo, getUserArticleList, getUserCommentList } from '@/api/user'
import axios from 'axios'
export default {
data() {
return {
userInfo: {},
userArticleList: [],
userCommentList: []
}
},
created() {
this.fetchUserData()
},
methods: {
async fetchUserData() {
try {
const [info, articles, comments] = await axios.all([
getUserInfo(),
getUserArticleList(),
getUserCommentList()
])
this.userInfo = info.data
this.userArticleList = articles.data
this.userCommentList = comments.data
} catch (error) {
console.error('获取用户数据失败', error)
}
}
}
}
</script>
在上述代码中,我们通过 axios.all
方法将三个网络请求包装成一个 Promise 对象数组,并使用 await
等待所有请求都完成。axios.spread
方法可以将所有请求的响应数据按照顺序解构出来,然后分别赋值给相应的数据属性进行展示。
封装网络请求的高级技巧
动态 API 地址
在实际项目中,可能会遇到需要根据不同的环境(开发环境、测试环境、生产环境)使用不同的 API 地址的情况。我们可以通过配置文件来实现动态 API 地址的设置。
在 src
目录下创建一个 config
文件夹,在其中创建一个 env.js
文件,用于存放不同环境的配置:
// src/config/env.js
const env = process.env.NODE_ENV
let baseURL
if (env === 'development') {
baseURL = 'http://localhost:3000'
} else if (env === 'test') {
baseURL = 'http://test.example.com'
} else {
baseURL = 'http://production.example.com'
}
export default {
baseURL
}
然后,在 main.js
文件中引入这个配置,并设置 Axios 的 baseURL
:
import Vue from 'vue'
import axios from 'axios'
import env from './config/env'
Vue.prototype.$axios = axios
// 设置 Axios 的 baseURL
axios.defaults.baseURL = env.baseURL
// 添加请求拦截器
axios.interceptors.request.use(
config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
// 添加响应拦截器
axios.interceptors.response.use(
response => {
if (response.status === 200) {
return response.data
} else {
throw new Error('响应状态码错误')
}
},
error => {
console.error('网络请求错误', error)
return Promise.reject(error)
}
)
这样,在封装网络请求时,只需要使用相对路径即可,Axios 会自动根据 baseURL
拼接完整的 API 地址。例如:
// src/api/article.js
import axios from 'axios'
// 封装获取文章列表的请求
export function getArticleList() {
return axios.get('/api/articles')
}
在开发环境中,实际请求的地址就是 http://localhost:3000/api/articles
;在生产环境中,实际请求的地址就是 http://production.example.com/api/articles
。
缓存网络请求结果
有些网络请求的数据在一段时间内不会发生变化,例如一些配置信息。为了避免重复请求相同的数据,可以对这些请求的结果进行缓存。
我们可以通过一个简单的缓存对象来实现:
// src/api/config.js
import axios from 'axios'
const configCache = {}
// 封装获取配置信息的请求
export function getConfig() {
if (configCache.config) {
return Promise.resolve(configCache.config)
}
return axios.get('/api/config').then(response => {
configCache.config = response.data
return response.data
})
}
在上述代码中,我们首先检查 configCache.config
是否存在,如果存在,则直接返回缓存的结果(通过 Promise.resolve
将其包装成一个已解决的 Promise 对象);如果不存在,则发起网络请求获取配置信息,并将结果缓存到 configCache.config
中。
在 Vue 组件中使用时:
<template>
<div>
<p>{{ config.title }}</p>
</div>
</template>
<script>
import { getConfig } from '@/api/config'
export default {
data() {
return {
config: {}
}
},
created() {
this.fetchConfig()
},
methods: {
async fetchConfig() {
try {
const data = await getConfig()
this.config = data
} catch (error) {
console.error('获取配置信息失败', error)
}
}
}
}
</script>
这样,第一次调用 getConfig
时会发起网络请求获取配置信息并缓存,后续再次调用时就直接从缓存中获取,避免了重复请求。
错误处理和重试机制
在网络请求过程中,可能会因为网络不稳定等原因导致请求失败。为了提高用户体验,可以为网络请求添加重试机制。
我们可以通过一个递归函数来实现重试:
// src/api/helper.js
import axios from 'axios'
// 封装带有重试机制的网络请求
export function requestWithRetry(url, options = {}, retries = 3) {
return new Promise((resolve, reject) => {
const makeRequest = (currentRetries) => {
axios(url, options)
.then(response => {
resolve(response.data)
})
.catch(error => {
if (currentRetries > 0) {
console.log(`请求失败,重试 ${currentRetries} 次`, error)
makeRequest(currentRetries - 1)
} else {
reject(error)
}
})
}
makeRequest(retries)
})
}
在上述代码中,requestWithRetry
函数接受请求 URL、请求选项和重试次数作为参数。通过递归调用 makeRequest
函数来实现重试机制。如果请求成功,直接 resolve 响应数据;如果请求失败且重试次数大于 0,则进行重试;如果重试次数用完仍失败,则 reject 错误。
在实际封装网络请求时,可以使用这个 requestWithRetry
函数:
// src/api/article.js
import { requestWithRetry } from './helper'
// 封装获取文章列表的请求,带有重试机制
export function getArticleList() {
return requestWithRetry('/api/articles', { method: 'get' }, 3)
}
在 Vue 组件中使用时:
<template>
<div>
<ul>
<li v-for="article in articleList" :key="article.id">{{ article.title }}</li>
</ul>
</div>
</template>
<script>
import { getArticleList } from '@/api/article'
export default {
data() {
return {
articleList: []
}
},
created() {
this.fetchArticleList()
},
methods: {
async fetchArticleList() {
try {
const data = await getArticleList()
this.articleList = data
} catch (error) {
console.error('获取文章列表失败,已达到最大重试次数', error)
}
}
}
}
</script>
这样,如果获取文章列表的请求失败,会自动重试 3 次,提高了请求成功的概率。
通过以上各种方式在 Vue 组件内封装网络请求,可以极大地提升代码的复用性、可维护性和健壮性,使前端开发更加高效和稳定。在实际项目中,可以根据具体需求灵活运用这些技巧,打造出更加优秀的前端应用。