MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue组件内封装网络请求提升代码复用性

2024-08-267.8k 阅读

为什么要在 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 提供了响应拦截器的功能,可以在响应被 thencatch 处理前对其进行拦截和处理。

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.allaxios.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 组件内封装网络请求,可以极大地提升代码的复用性、可维护性和健壮性,使前端开发更加高效和稳定。在实际项目中,可以根据具体需求灵活运用这些技巧,打造出更加优秀的前端应用。