导读
先捋一捋前后端交互请求的API方式都有哪些吧!
首先明确我们现在学习和使用的是Vue3.js,那么选用VueRequest库自然是比较不错的选择,当然了Fetch和Axios还是会在合适的地方使用的,即便是繁杂的原始xhr也会在某些高级功能的时候需要我们使用
大体如下:
Fetch
Fetch是浏览器提供的用于进行网络请求的API,使用起来相对简单。它返回一个Promise对象,支持请求和响应的headers、body、response type等属性的设置。
Fetch的优点在于它是浏览器自带的API,不需要安装额外的库或插件,而且可以轻松地发送和接收JSON数据。
XMLHttpRequest (XHR)
XMLHttpRequest (XHR)是一个早期的用于进行异步HTTP请求的API。它可以从服务器获取数据,也可以将数据发送到服务器。XHR的优点在于它的支持性较好,可以在所有主流的浏览器中使用。
XHR的缺点在于,它的API较为繁琐,需要手动设置请求头、发送数据等。而且在处理复杂的请求时,代码会变得复杂且难以维护。
Axios
Axios是一个流行的用于进行HTTP请求的JavaScript库。它可以在浏览器和Node.js中使用,支持Promise API、请求和响应拦截器、数据转换、自动CSRF等功能。Axios还提供了一些方便的API,例如axios.get、axios.post等。
Axios的优点在于它的API简单易用,而且提供了许多有用的功能,例如请求拦截器和响应拦截器,可以方便地在请求或响应被发送或接收时进行一些处理。此外,Axios还支持取消请求,可以在请求已经被发送但是服务器还未响应时取消该请求。
vue-request
vue-request是一个基于Vue.js的第三方组件库,提供了一些常用的HTTP请求和响应处理功能。它封装了XMLHttpRequest和fetch,支持自定义的拦截器和自动重试。
vue-request的优点在于它是基于Vue.js的,可以方便地集成到Vue.js项目中。此外,vue-request提供了一些额外的功能,例如自动重试和拦截器,可以提高代码的可重用性和可维护性。
浏览器中有两套 API 可以和后端交互,发送请求、接收响应,fetch api 前面我们已经介绍过了,另一套 api 是 xhr,基本用法如下
const xhr = new XMLHttpRequest()
xhr.onload = function() {
    console.log(xhr.response)
}
xhr.open('GET', 'http://localhost:8080/api/students')
xhr.responseType = "json"
xhr.send()
但这套 api 虽然功能强大,但比较老,不直接支持 Promise,因此有必要对其进行改造
function get(url: string) {
  return new Promise((resolve, reject)=>{
    const xhr = new XMLHttpRequest()
    xhr.onload = function() {
      if(xhr.status === 200){
        resolve(xhr.response)
      } else if(xhr.status === 404) {
        reject(xhr.response)
      } // 其它情况也需考虑,这里简化处理
    }
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.send()
  })
}
调用示例1:同步接收结果,不走代理
try {
  const resp = await get("http://localhost:8080/api/students")
  console.log(resp)
} catch (e) {
  console.error(e)
}
调用示例2:走代理
try {
  const resp = await get('/api/students')
  console.log(resp)  
} catch(e) {
  console.log(e)
}
走代理明显慢不少
axios 就是对 xhr api 的封装,手法与前面例子类似
安装
npm install axios
一个简单的例子
<script setup lang="ts">
import { ref, onMounted } from "vue";
import axios from "axios";
let count = ref(0);
async function getStudents() {
  try {
    const resp = await axios.get("/api/students");
    count.value = resp.data.data.length;
  } catch (e) {
    console.log(e);
  }
}
onMounted(() => {
  getStudents()
})
</script>
<template>
  <h2>学生人数为:{{ count }}</h2>
</template>
再来看一个 post 例子
<script setup lang="ts">
import { ref } from "vue";
import axios from "axios";
const student = ref({
  name: '',
  sex: '男',
  age: 18
})
async function addStudent() {
  console.log(student.value)
  const resp = await axios.post('/api/students', student.value)
  console.log(resp.data.data)
}
</script>
<template>
  <div>
    <div>
      <input type="text" placeholder="请输入姓名" v-model="student.name"/>
    </div>
    <div>
      <label for="">请选择性别</label>
      男 <input type="radio" value="男" v-model="student.sex"/> 
      女 <input type="radio" value="女" v-model="student.sex"/>
    </div>
    <div>
      <input type="number" placeholder="请输入年龄" v-model="student.age"/>
    </div>
    <div>
      <input type="button" value="添加" @click="addStudent"/>
    </div>
  </div>
</template>
<style scoped>
div {
  font-size: 14px;
}
</style>
http://localhost:8080,http://itheima.com这就要求我们区分开发环境和生产环境,这件事交给构建工具 vite 来做
默认情况下,vite 支持上面两种环境,分别对应根目录下两个配置文件 
针对以上需求,分别在两个文件中加入
VITE_BACKEND_API_BASE_URL = 'http://localhost:8080'
和
VITE_BACKEND_API_BASE_URL = 'http://itheima.com'
然后在代码中使用 vite 给我们提供的特殊对象 import.meta.env,就可以获取到 VITE_BACKEND_API_BASE_URL 在不同环境下的值
import.meta.env.VITE_BACKEND_API_BASE_URL
默认情况下,不能智能提示自定义的环境变量,做如下配置:新增文件 src/env.d.ts 并添加如下内容
/// <reference types="vite/client" />
interface ImportMetaEnv {
  readonly VITE_BACKEND_API_BASE_URL: string
  // 更多环境变量...
}
interface ImportMeta {
  readonly env: ImportMetaEnv
}
可以自己创建一个 axios 对象,方便添加默认设置,新建文件 /src/api/request.ts
// 创建新的 axios 对象
import axios from 'axios'
const _axios = axios.create({
  baseURL: import.meta.env.VITE_BACKEND_API_BASE_URL
})
export default _axios
然后在其它组件中引用这个 ts 文件,例如 /src/views/E8.vue,就不用自己拼接路径前缀了
<script setup lang="ts">
import axios from '../api/request'
// ...
await axios.post('/api/students', ...)    
</script>
// 创建新的 axios 对象
import axios from 'axios'
const _axios = axios.create({
  baseURL: import.meta.env.VITE_BACKEND_API_BASE_URL
})
// 请求拦截器
_axios.interceptors.request.use(
  (config)=>{ // 统一添加请求头
    config.headers = {
      Authorization: 'aaa.bbb.ccc'
    }
    return config
  },
  (error)=>{ // 请求出错时的处理
    return Promise.reject(error)
  }
)
// 响应拦截器
_axios.interceptors.response.use(
  (response)=>{ // 状态码  2xx
    // 这里的code是自定义的错误码
    if(response.data.code === 200) {
      return response
    }     
    else if(response.data.code === 401) {       
      // 情况1
      return Promise.resolve({})
    }
    // ... 
  },
  (error)=>{ // 状态码 > 2xx, 400,401,403,404,500
    console.error(error) // 处理了异常
    if(error.response.status === 400) {
      // 情况1
    } else if(error.response.status === 401) {
      // 情况2
    } 
    // ...
    return Promise.resolve({})
  }
)
export default _axios
处理响应时,又分成两种情况
另外
响应式的 axios 封装,官网地址 一个 Vue 请求库 | VueRequest (attojs.org)
首先安装 vue-request
npm install vue-request@next
组件
<template>
  <h3 v-if="students.length === 0">暂无数据</h3>
  <ul v-else>
    <li v-for="s of students" :key="s.id">
      <span>{{s.name}}</span>
      <span>{{s.sex}}</span>
      <span>{{s.age}}</span>
    </li>
  </ul>
</template>
<script setup lang="ts">
import axios from "../api/request"
import { useRequest } from 'vue-request'
import { computed } from 'vue'
import { AxiosRespList, Student } from '../model/Model8080'
// data 代表就是 axios 的响应对象
const { data } = useRequest<AxiosRespList<Student>>(() => axios.get('/api/students'))
const students = computed(()=>{
  return data?.value?.data.data || []
})
</script>
<style scoped>
ul li {
  list-style: none;
  font-family: "华文行楷";
}
li span:nth-child(1) {
  font-size: 24px;
}
li span:nth-child(2) {
  font-size: 12px;
  color: crimson;
  vertical-align: bottom;
}
li span:nth-child(3) {
  font-size: 12px;
  color: darkblue;
  vertical-align: top;
}
</style>
data.value 的取值一开始是 undefined,随着响应返回变成 axios 的响应对象
用 computed 进行适配
在 src/model/Model8080.ts 中补充类型说明
export interface StudentQueryDto {
  name?: string,
  sex?: string,
  age?: string, // 18,20
  page: number,
  size: number
}
js 中类似于 18,20 这样以逗号分隔字符串,会在 get 传参时转换为 java 中的整数数组
编写组件
<template>
  <input type="text" placeholder="请输入姓名" v-model="dto.name">
  <select v-model="dto.sex">
    <option value="" selected>请选择性别</option>
    <option value="男">男</option>
    <option value="女">女</option>
  </select>
  <input type="text" placeholder="请输入年龄范围" v-model="dto.age">
  <br>
  <input type="text" placeholder="请输入页码" v-model="dto.page">
  <input type="text" placeholder="请输入页大小" v-model="dto.size">
  <input type="button" value="搜索" @click="search">
  <hr>
  <h3 v-if="students.length === 0">暂无数据</h3>
  <ul v-else>
    <li v-for="s of students" :key="s.id">
      <span>{{s.name}}</span>
      <span>{{s.sex}}</span>
      <span>{{s.age}}</span>
    </li>
  </ul>
  <hr>
  总记录数{{total}} 总页数{{totalPage}}
</template>
<script setup lang="ts">
import axios from "../api/request"
import { usePagination } from 'vue-request'
import { computed, ref } from 'vue'
import { AxiosRespPage, Student, StudentQueryDto } from '../model/Model8080'
const dto = ref<StudentQueryDto>({name:'', sex:'', age:'', page:1, size:5})
// data 代表就是 axios 的响应对象
// 泛型参数1: 响应类型
// 泛型参数2: 请求类型
const { data, total, totalPage, run } = usePagination<AxiosRespPage<Student>, StudentQueryDto[]>(
  (d) => axios.get('/api/students/q', {params: d}), // 箭头函数
  {
    defaultParams: [ dto.value ], // 默认参数, 会作为参数传递给上面的箭头函数
    pagination: {
      currentKey: 'page', // 指明当前页属性
      pageSizeKey: 'size', // 指明页大小属性
      totalKey: 'data.data.total' // 指明总记录数属性
    } 
  } // 选项
)
const students = computed(()=>{
  return data?.value?.data.data.list || []
})
function search() {
  run(dto.value) // 会作为参数传递给usePagination的箭头函数
}
</script>
<style scoped>
ul li {
  list-style: none;
  font-family: "华文行楷";
}
li span:nth-child(1) {
  font-size: 24px;
}
li span:nth-child(2) {
  font-size: 12px;
  color: crimson;
  vertical-align: bottom;
}
li span:nth-child(3) {
  font-size: 12px;
  color: darkblue;
  vertical-align: top;
}
input,select {
  width: 100px;
}
</style>
usePagination 只需要定义一次,后续还想用它内部的 axios 发请求,只需调用 run 函数