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

Vue网络请求 基于Composition API封装请求函数

2023-07-207.9k 阅读

一、Vue 与 Composition API 概述

Vue.js 是一款流行的 JavaScript 前端框架,它提供了一系列简洁高效的工具来构建用户界面。随着 Vue 3 的发布,Composition API 成为了一个重要的新特性。Composition API 允许开发者以一种更加灵活和可复用的方式组织组件逻辑。

在传统的 Vue 组件中,我们通常使用 datamethodscomputed 等选项来定义组件的状态和行为。然而,当组件变得复杂时,这些选项可能会导致代码逻辑分散,难以维护。Composition API 通过提供 setup 函数以及一系列的响应式 API,如 refreactive 等,使得我们可以将相关的逻辑代码组合在一起,提高代码的可读性和可维护性。

二、网络请求在前端开发中的重要性

在现代前端应用开发中,网络请求是不可或缺的一部分。前端应用需要与后端服务器进行数据交互,以获取最新的数据展示给用户,或者将用户输入的数据发送到服务器进行处理。常见的网络请求场景包括获取用户信息、加载列表数据、提交表单数据等。

在 Vue 应用中,我们可以使用多种库来处理网络请求,其中最常用的是 axiosaxios 是一个基于 Promise 的 HTTP 客户端,它在浏览器和 Node.js 中都可以使用,并且提供了简洁易用的 API,支持拦截请求和响应、取消请求等功能。

三、基于 Composition API 封装请求函数的优势

  1. 代码复用性:通过封装请求函数,我们可以在多个组件中复用相同的网络请求逻辑。例如,对于获取用户信息的请求,我们只需要封装一次,然后在不同的组件中调用这个封装好的函数即可,避免了重复编写相似的请求代码。
  2. 逻辑清晰:将网络请求逻辑封装在单独的函数中,使得组件的 setup 函数更加简洁。组件只需要关心如何使用请求返回的数据,而不需要关注具体的请求细节,如请求地址、请求方法、请求头设置等。
  3. 便于维护和更新:当后端 API 发生变化时,我们只需要在封装的请求函数中进行修改,而不需要在每个使用该请求的组件中逐一修改,降低了维护成本。

四、使用 axios 进行网络请求基础

在开始封装请求函数之前,我们需要先安装并引入 axios。假设我们已经创建了一个 Vue 项目,可以通过以下命令安装 axios

npm install axios

然后在项目中引入 axios,通常在 main.js 中进行全局引入:

import { createApp } from 'vue'
import axios from 'axios'
import App from './App.vue'

const app = createApp(App)
app.config.globalProperties.$axios = axios
app.mount('#app')

这样,在组件中就可以通过 this.$axios 来使用 axios 进行网络请求了。例如,发送一个 GET 请求获取数据:

<template>
  <div>
    <button @click="fetchData">获取数据</button>
    <div v-if="data">
      <pre>{{ data }}</pre>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      data: null
    }
  },
  methods: {
    async fetchData() {
      try {
        const response = await this.$axios.get('/api/data')
        this.data = response.data
      } catch (error) {
        console.error('请求失败', error)
      }
    }
  }
}
</script>

上述代码中,我们在按钮点击时发送一个 GET 请求到 /api/data 地址,并将返回的数据显示在页面上。如果请求失败,会在控制台打印错误信息。

五、基于 Composition API 封装 GET 请求函数

  1. 创建请求函数文件:首先,我们在项目中创建一个 api 文件夹,用于存放所有的请求函数。在 api 文件夹下创建一个 user.js 文件,用于封装与用户相关的请求函数。
import { ref } from 'vue'
import axios from 'axios'

export const useGetUserInfo = () => {
  const userInfo = ref(null)
  const isLoading = ref(false)
  const error = ref(null)

  const fetchUserInfo = async () => {
    isLoading.value = true
    try {
      const response = await axios.get('/api/user/info')
      userInfo.value = response.data
    } catch (e) {
      error.value = e
    } finally {
      isLoading.value = false
    }
  }

  return {
    userInfo,
    isLoading,
    error,
    fetchUserInfo
  }
}
  1. 在组件中使用封装的函数:在组件的 setup 函数中引入并使用这个封装好的函数。
<template>
  <div>
    <button @click="fetchUserInfo">获取用户信息</button>
    <div v-if="isLoading">加载中...</div>
    <div v-if="userInfo">
      <pre>{{ userInfo }}</pre>
    </div>
    <div v-if="error">
      <pre>{{ error }}</pre>
    </div>
  </div>
</template>

<script>
import { useGetUserInfo } from '@/api/user'

export default {
  setup() {
    const { userInfo, isLoading, error, fetchUserInfo } = useGetUserInfo()
    return {
      userInfo,
      isLoading,
      error,
      fetchUserInfo
    }
  }
}
</script>

在上述代码中,我们封装的 useGetUserInfo 函数返回了用户信息 userInfo、加载状态 isLoading、错误信息 error 以及发起请求的 fetchUserInfo 函数。组件通过解构获取这些数据和函数,并在模板中进行相应的展示。

六、基于 Composition API 封装 POST 请求函数

  1. 封装 POST 请求函数:继续在 api/user.js 文件中封装一个用于提交用户登录信息的 POST 请求函数。
export const useUserLogin = () => {
  const loginResult = ref(null)
  const isLoading = ref(false)
  const error = ref(null)

  const userLogin = async (username, password) => {
    isLoading.value = true
    try {
      const response = await axios.post('/api/user/login', {
        username,
        password
      })
      loginResult.value = response.data
    } catch (e) {
      error.value = e
    } finally {
      isLoading.value = false
    }
  }

  return {
    loginResult,
    isLoading,
    error,
    userLogin
  }
}
  1. 在组件中使用 POST 请求函数
<template>
  <div>
    <input v-model="username" placeholder="用户名">
    <input v-model="password" placeholder="密码" type="password">
    <button @click="handleLogin">登录</button>
    <div v-if="isLoading">加载中...</div>
    <div v-if="loginResult">
      <pre>{{ loginResult }}</pre>
    </div>
    <div v-if="error">
      <pre>{{ error }}</pre>
    </div>
  </div>
</template>

<script>
import { ref } from 'vue'
import { useUserLogin } from '@/api/user'

export default {
  setup() {
    const username = ref('')
    const password = ref('')
    const { loginResult, isLoading, error, userLogin } = useUserLogin()

    const handleLogin = () => {
      userLogin(username.value, password.value)
    }

    return {
      username,
      password,
      loginResult,
      isLoading,
      error,
      handleLogin
    }
  }
}
</script>

在这个例子中,useUserLogin 函数封装了用户登录的 POST 请求逻辑,组件通过调用 userLogin 函数并传入用户名和密码来发起登录请求,并根据返回结果进行相应的展示。

七、请求拦截器与响应拦截器的封装

  1. 请求拦截器:请求拦截器可以在请求发送之前对请求进行一些处理,例如添加请求头。在 main.js 中设置请求拦截器:
import { createApp } from 'vue'
import axios from 'axios'
import App from './App.vue'

const app = createApp(App)

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)
})

app.config.globalProperties.$axios = axios
app.mount('#app')
  1. 响应拦截器:响应拦截器可以在接收到响应之后对响应进行处理,例如处理错误信息。同样在 main.js 中设置响应拦截器:
axios.interceptors.response.use(response => {
  return response
}, error => {
  if (error.response.status === 401) {
    // 处理未授权错误,例如跳转到登录页面
    router.push('/login')
  }
  return Promise.reject(error)
})
  1. 在封装的请求函数中体现拦截器作用:经过上述设置,我们封装的请求函数在发送请求和接收响应时就会自动应用这些拦截器的逻辑。例如,当用户登录成功后,将 token 存储在 localStorage 中,后续的请求就会自动带上 Authorization 头。

八、处理请求缓存

在一些场景下,我们希望对某些请求进行缓存,避免重复请求相同的数据。以获取用户信息为例,我们可以在封装的请求函数中添加缓存逻辑。

  1. 添加缓存逻辑到请求函数:修改 useGetUserInfo 函数。
let cachedUserInfo = null

export const useGetUserInfo = () => {
  const userInfo = ref(cachedUserInfo)
  const isLoading = ref(false)
  const error = ref(null)

  const fetchUserInfo = async () => {
    if (cachedUserInfo) {
      userInfo.value = cachedUserInfo
      return
    }
    isLoading.value = true
    try {
      const response = await axios.get('/api/user/info')
      userInfo.value = response.data
      cachedUserInfo = userInfo.value
    } catch (e) {
      error.value = e
    } finally {
      isLoading.value = false
    }
  }

  return {
    userInfo,
    isLoading,
    error,
    fetchUserInfo
  }
}
  1. 缓存的更新与失效:当用户信息发生变化时,例如用户修改了个人资料,我们需要更新缓存。可以在处理用户信息更新的逻辑中添加 cachedUserInfo = null,这样下次获取用户信息时就会重新请求数据。

九、错误处理与提示优化

  1. 集中处理错误信息:在响应拦截器中,我们可以对不同类型的错误进行统一处理,并给出友好的提示信息。例如:
axios.interceptors.response.use(response => {
  return response
}, error => {
  let errorMessage = '请求发生错误'
  if (error.response) {
    switch (error.response.status) {
      case 400:
        errorMessage = '请求参数错误'
        break
      case 401:
        errorMessage = '未授权,请登录'
        break
      case 404:
        errorMessage = '资源未找到'
        break
      case 500:
        errorMessage = '服务器内部错误'
        break
      default:
        errorMessage = `错误状态码: ${error.response.status}`
    }
  }
  // 使用 Vue 的全局提示组件(假设已经引入并配置好)
  app.config.globalProperties.$message.error(errorMessage)
  return Promise.reject(error)
})
  1. 在组件中处理特定错误:除了全局的错误处理,组件也可以根据自身需求对某些特定错误进行处理。例如,在获取用户信息的组件中,如果因为网络问题导致请求失败,我们可以提示用户检查网络连接。
<template>
  <div>
    <button @click="fetchUserInfo">获取用户信息</button>
    <div v-if="isLoading">加载中...</div>
    <div v-if="userInfo">
      <pre>{{ userInfo }}</pre>
    </div>
    <div v-if="error && error.code === 'ECONNABORTED'">
      网络连接超时,请检查网络
    </div>
    <div v-if="error && error.code!== 'ECONNABORTED'">
      <pre>{{ error }}</pre>
    </div>
  </div>
</template>

<script>
import { useGetUserInfo } from '@/api/user'

export default {
  setup() {
    const { userInfo, isLoading, error, fetchUserInfo } = useGetUserInfo()
    return {
      userInfo,
      isLoading,
      error,
      fetchUserInfo
    }
  }
}
</script>

十、结合 Vuex 管理请求状态(可选)

如果项目中使用了 Vuex,我们可以将请求的状态(如加载中、请求结果、错误信息等)存储在 Vuex 的状态树中,以便在多个组件之间共享和管理。

  1. 在 Vuex 中定义模块:假设我们在 store/modules/user.js 中定义与用户相关的模块。
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    userInfo: null,
    isLoading: false,
    error: null
  }),
  actions: {
    async fetchUserInfo() {
      this.isLoading = true
      try {
        const response = await axios.get('/api/user/info')
        this.userInfo = response.data
      } catch (e) {
        this.error = e
      } finally {
        this.isLoading = false
      }
    }
  }
})
  1. 在组件中使用 Vuex 模块
<template>
  <div>
    <button @click="fetchUserInfo">获取用户信息</button>
    <div v-if="isLoading">加载中...</div>
    <div v-if="userInfo">
      <pre>{{ userInfo }}</pre>
    </div>
    <div v-if="error">
      <pre>{{ error }}</pre>
    </div>
  </div>
</template>

<script>
import { useUserStore } from '@/store/modules/user'

export default {
  setup() {
    const userStore = useUserStore()
    const fetchUserInfo = () => {
      userStore.fetchUserInfo()
    }
    return {
      userInfo: userStore.userInfo,
      isLoading: userStore.isLoading,
      error: userStore.error,
      fetchUserInfo
    }
  }
}
</script>

通过结合 Vuex,我们可以更方便地在整个应用中管理和共享请求状态,同时也可以利用 Vuex 的其他特性,如状态持久化等。

通过以上步骤,我们全面地介绍了基于 Vue 的 Composition API 封装网络请求函数的方法,包括 GET 和 POST 请求的封装、请求拦截器与响应拦截器的使用、请求缓存处理、错误处理优化以及结合 Vuex 管理请求状态等内容。这些技术可以帮助我们构建更加健壮、高效和可维护的前端应用。