无良博文害我误入歧途
一开始为了省事,不去读文档,就去社区看了很多的请求封装,因为一直是用vue开发,都有封装请求库的习惯,看了好多的封装nuxt请求的文章,基本上都是对useFetch的直接封装,又去官网看了一眼确实是有那么一丝道理,请求拦截,相应啥都有,就开始照搬照抄。
const { data, status, error, refresh, clear } = await useFetch('/api/auth/login', {
onRequest({ request, options }) {
// 设置请求头
// 注意:这依赖于 ofetch >= 1.4.0,你可能需要刷新你的锁文件
options.headers.set('Authorization', '...')
},
onRequestError({ request, options, error }) {
// 处理请求错误
},
onResponse({ request, response, options }) {
// 处理响应数据
localStorage.setItem('token', response._data.token)
},
onResponseError({ request, response, options }) {
// 处理响应错误
}
})
因此得到一代请求的封装,但是其实全是坑基本上用不了,但是一直在开发博客的其他页面也就没太在意。
import { type UseFetchOptions, type AsyncData, useCookie, useFetch, useRuntimeConfig } from 'nuxt/app'
// import { ElMessage } from 'element-plus'
class Request {
// 基础URL
private baseURL: string
constructor() {
const runtimeConfig = useRuntimeConfig()
this.baseURL = runtimeConfig.public.apiBase as string
}
awaitTo<T>(promise: Promise<T>) {
return promise.then((data: T)=> [null, data]).catch((err: any)=> [err, null])
}
/**
* 核心请求方法(包含完整拦截器)
* @param url 请求地址
* @param options 请求配置
*/
async request<T = any>(url: string, options: UseFetchOptions<T> = {} as UseFetchOptions<T>) {
// 1. 基础配置
const fetchOptions: UseFetchOptions<T> = {
baseURL: this.baseURL,
timeout: 10000,
method: options.method || 'GET',
params: options.params,
body: options.body,
server: options.server ?? true,
immediate: options.immediate ?? true,
...options,
onRequest({ request, options }) {
// 设置请求头
// 注意:这依赖于 ofetch >= 1.4.0,你可能需要刷新你的锁文件
// options.headers.set('Content-Type', 'application/json')
},
onRequestError({ request, options, error }) {
// 处理请求错误
},
// 响应成功拦截器
onResponse({ request, response, options }) {
// console.log('response===========>', response)
response._data = response._data.data
},
onResponseError({ request, response, options }) {
// 处理响应错误
}
}
// 2. 执行原生useFetch
const res = this.awaitTo(useFetch<T>(url, fetchOptions as any))
return res
}
// ========== 快捷请求方法 ==========
async get<T = any>(url: string, params?: any, options: UseFetchOptions<T> = {} as UseFetchOptions<T>) {
return this.request<T>(url, { ...options, method: 'GET', params })
}
async post<T = any>(url: string, data?: any, options: UseFetchOptions<T> = {} as UseFetchOptions<T>) {
return this.request<T>(url, { ...options, method: 'POST', body: data })
}
async put<T = any>(url: string, data?: any, options: UseFetchOptions<T> = {} as UseFetchOptions<T>) {
return this.request<T>(url, { ...options, method: 'PUT', body: data })
}
async delete<T = any>(url: string, params?: any, options: UseFetchOptions<T> = {} as UseFetchOptions<T>) {
return this.request<T>(url, { ...options, method: 'DELETE', params })
}
}
// 全局单例实例
export const request = new Request()
部署项目后发现请求根本用不了
后面部署后,每个请求都会重复一次,而且分页切换时候还会报错,才决定好好读读文档来解决请求问题。
$fetch根本不能作为直接获取数据的请求,可能会导致数据被获取两次:一次在服务器(用于渲染 HTML),另一次在客户端(当 HTML 被激活时)。这可能会导致激活问题、增加交互时间并引发不可预测的行为。useFetch 会确保请求在服务器上发生,并正确转发到浏览器。$fetch 没有这种机制,更适合仅从浏览器发起请求的场景。官网也给了例子,说useFetch 会确保请求在服务器上发生,并正确转发到浏览器。$fetch 没有这种机制,更适合仅从浏览器发起请求的场景。
<script setup lang="ts">
const { data } = await useFetch('/api/data')
async function handleFormSubmit() {
const res = await $fetch('/api/submit', {
method: 'POST',
body: {
// 我的表单数据
}
})
}
</script>
<template>
<div v-if="data == undefined">
无数据
</div>
<div v-else>
<form @submit="handleFormSubmit">
<!-- 表单输入标签 -->
</form>
</div>
</template>
正确的请求封装
其实官网上一开始就有了如何封装请求,只是他对数据获取有过多的陈述,导致我忽略这个他的封装指南,加之useFetch上有请求拦截等,我对封装请求的刻板印象,和那些无良博文,让我一度认为封装这个api是对的,知道自己亲身使用才发现不行,官网上明确的说明了,只能通过封装ofetch这个库,来进行统一的请求处理,但是页面的水合又不能使用这个请求,作为初始化请求,因此初始化请求还是要用到useFecth或者useAsyncData来做,只是将封装的ofecth作为请求参数传给这两个api即可。官网封装文档
- 封装
ofetch统一处理请求,用于从客户端发起请求// plugins/api.ts 封装成一个插件 export default defineNuxtPlugin((nuxtApp) => { const { session } = useUserSession() const api = $fetch.create({ baseURL: 'https://api.nuxt.com', onRequest({ request, options, error }) { if (session.value?.token) { // 注意:这依赖于 ofetch >= 1.4.0 - 你可能需要刷新你的 lockfile options.headers.set('Authorization', `Bearer ${session.value?.token}`) } }, async onResponseError({ response }) { if (response.status === 401) { await nuxtApp.runWithContext(() => navigateTo('/login')) } } }) // 通过 useNuxtApp().$api 暴露 return { provide: { api } } }) - 再通过上面这个插件封装
useFecth,用于处理页面初始数据的获取import type { UseFetchOptions } from 'nuxt/app' export function useAPI<T>( url: string | (() => string), options?: UseFetchOptions<T>, ) { return useFetch(url, { ...options, $fetch: useNuxtApp().$api as typeof $fetch }) } - 页面请求的正确姿势
import { ArticleApi, TagsApi } from '@/api' import type { IArticle, ITags } from '@/api' const { $api } = useNuxtApp() const pageQuery = ref({ current: 1, size: 10, total: 0 }) const articleList = ref<IArticle[]>([]) /** * 获取页面初始化的数据 */ const { data } = await useAPI<IPageResult<IArticle[]>>(ArticleApi.ArticlePage, { params: { current: pageQuery.value.current, size: pageQuery.value.size } }) /** * 分页切换时,获取数据 */ async function getArticleList() { const { current, size } = pageQuery.value // 直接调用$api即可 const res = await $api<IPageResult<IArticle[]>>(ArticleApi.ArticlePage, { params: { current: current, size }}) manageArticleData(res) }