Vue.js 生态最佳请求方案
迄今为止,Vue.js 生态(不包括 Nuxt)最佳的请求方案我当前推崇 Axios + Pinia Colada。
背景
Vue.js 作为当前全世界最流行的前端应用开发框架之一,有无数的前端应用构建在其之上。而在绝大部分 CSR(客户端渲染)的前端应用中,以何种代码组织形式与后端交互是一个永恒的技术点。
基础请求库
Axios
Axios 是一个基于 promise 的网络请求库,可用于浏览器和 Node.js(在服务端它使用原生 Node.js http 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequest),它使用简单,包体积小且提供了易于扩展的接口。Axios 几乎是当前最流行的前端请求库。
ofetch
ofetch 是 fetch API 的增强版,是 Nuxt 框架的底层请求依赖。
传统请求封装思路
当前,在 Vue.js app 中最常见的的请求模块都是基于 Axios 进行封装:
import axios from 'axios'
export const request = axios.create({
// 通常使用环境变量区分本地代理和线上 API
baseURL: 'xxx',
// 设置 Authorization 等
headers: {},
timeout: 10000,
})
// 请求拦截器
request.interceptors.request.use((config) => {
return config
}, (error) => {
return Promise.reject(error)
})
// 响应拦截器
request.interceptors.response.use((response) => {
return response.data
}, (error) => {
return Promise.reject(error)
})2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { request } from '@/utils/request'
export function getBusinessByIdApi(id) {
if (!id) {
return
}
return request.get(`/business?id=${id}`)
}2
3
4
5
6
7
8
9
<script lang="ts" setup>
import { getBusinessByIdApi } from '@/apis/business'
import { ref, shallowRef, watch } from 'vue'
import { useRoute } from 'vue-router'
const detail = shallowRef()
const loading = ref(false)
function refreshDetail(id) {
if (loading.value) {
return
}
loading.value = true
getBusinessByIdApi(id).then((data) => {
detail.value = data
}).catch((e) => {
console.error(e)
}).finally(() => {
loading.value = false
})
}
const route = useRoute()
watch(() => route.query.id, (id) => {
refreshDetail(id)
}, {
immediate: true
})
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
显然,如果遵循一定范式来调用 API 的话,整个流程还是较为繁琐的,从 Axios 的封装到数据的获取,需要依次通过三个模块节点,并且在 Vue 组件中获取数据的代码总是重复代码(样本代码),每次发起请求都需要从头写一遍,这就留下了优化空间。
Pinia Colada
核心 API
Pinia Colada 基于 Pinia 提供了一套当前 Vue.js 生态最优的异步状态管理方案,它利用 Vue.js 提供的逻辑复用方案 composables 避免了业务为了获取数据而写大量的样本代码,并透明支持缓存以及请求去重等实用功能。
useQuery
useQuery 是 Pinia Colada 提供的基础查询用法,主要用于获取数据的场景中:
<script lang="ts" setup>
import { getBusinessByIdApi } from '@/apis/business'
import { useQuery } from '@pinia/colada'
import { ref, shallowRef, watch } from 'vue'
import { useRoute } from 'vue-router'
const detail = shallowRef()
const loading = ref(false)
function refreshDetail(id) {
if (loading.value) {
return
}
loading.value = true
getBusinessByIdApi(id).then((data) => {
detail.value = data
}).catch((e) => {
console.error(e)
}).finally(() => {
loading.value = false
})
}
const route = useRoute()
const { data, isLoading, refresh } = useQuery({
key: () => ['getBusinessByIdApi', route.query.id],
query: () => getBusinessByIdApi(route.query.id)
})
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
useMutation
useMutation 主要用于触发和跟踪具有副作用的一步操作状态,主要用于写入数据的场景:
<script lang="ts" setup>
import { createBusinessApi } from '@/apis/business'
import { useMutation } from '@pinia/colada'
const {
mutate,
isLoading
} = useMutation({
mutation: data => createBusinessApi(data)
})
</script>2
3
4
5
6
7
8
9
10
11
import { request } from '@/utils/request'
export function createBusinessApi(data) {
if (!id) {
return
}
return request.post(`/business`, data)
}2
3
4
5
6
7
8
9
核心功能
- useQuery 获取的数据会利用 Pinia 进行缓存,再次调用时会优先使用缓存值
- useQuery 同一时间发起的请求,当 key 值相同时,会去重处理
总结
Pinia Colada 是在 Vue.js 3.x 版本背景下的新星,它拥抱 Composition API 并提供了一种更加有组织、模块化的代码结构,一边大幅度减少了业务请求的样本代码,一边又融入了缓存、请求去重等实用功能。个人认为在当前全面拥抱 Vue.js 3.x 的背景下,相比传统写法来说是一种更加合理的代码组织形式。
