Nuxt3 useFetch封装最佳实践
文章目录

ajax、axios、fetch的发展
- ajax
在传统开发中,使用XMLHttpRequest对象创建的网络请求居多,这种异步技术称之为ajax。后来jQuery封装了它,但是这项技术有一个很大的问题,ajax中需要传回调函数,由于http请求是在异步队列中运行的,请求到的数据需要返回,就需要使用回调函数,但是这种方法就有很大的问题,那就是回调地狱,需要写大量的回调函数。直到后来Promise的出现解决回调地狱的问题,Promise就是为异步而生的.
- axios
这个时候新的库就出现了——axios。axios是基于Promise对ajax的封装,里面通过了对应的配置和拦截器,分别是请求拦截器和响应拦截器。而node端的axios是对http库的封装
- fetch
fetch 是一种使用
promise为构建块的现代异步网络请求方法.是当今进行异步网络请求的新标准.除了IE之外,在各大浏览器中的兼容性都还可以,在caniuse上查询fetch的浏览器兼容性,不支持的浏览器可以使用fetch polyfill.其本质是一种标准,该标准定义了请求,响应和绑定的流程,还定义了Fetch的JavaScript API.而Fetch API 提供了 fetch() 方法.它被定义在BOM的window对象中,返回一个Promise对象,因此我们能够对返回的结果进行检索.
什么是useFetch
简单来说useFetch就是对fetch的封装。
为什么不使用axios?
在nuxt2中我们能看见nuxt-axios库,也就是axios对nuxt的封装,但是这种其实是有问题的,刚刚我们说到axios在浏览器端和node端是对ajax和http俩个库的封装,而nuxt是有一个两次渲染的这样在服务端和浏览器端的时候就是俩个不通的库,不利于优化,而fetch与其说是一个新的http库,不如说是一个新标准,在esm中已经将它列入一个标准化了
Nuxt3 数据获取介绍
Nuxt 提供了两个组合函数和一个内置库,用于在浏览器或服务器环境中执行数据获取:useFetch、useAsyncData 和 $fetch。
简而言之: useFetch 是在组件设置函数中处理数据获取的最简单方法。
这个可组合函数提供了一个方便的封装,包装了useAsyncData和$fetch。它根据 URL 和 fetch 选项自动生成一个键,根据服务器路由提供请求 URL 的类型提示,并推断 API 响应类型。 $fetch 可以根据用户交互进行网络请求。
useAsyncData 结合 $fetch,提供了更精细的控制。
useFetch 和 useAsyncData 共享一组常见的选项和模式,在后面的章节中我们将详细介绍。
为什么需要特定的组合函数?
使用像 Nuxt 这样的框架可以在客户端和服务器环境中执行调用和呈现页面时,必须解决一些问题。这就是为什么 Nuxt 提供了组合函数来封装查询,而不是让开发者仅依赖于 $fetch 调用。
useFetch封装最佳实践
import type {FetchError, FetchResponse, SearchParameters} from 'ofetch';
import {hash} from 'ohash';
import type {AsyncData, UseFetchOptions} from '#app';
import type {KeysOf} from '#app/composables/asyncData';
type UrlType = string | Request | Ref<string | Request> | (() => string | Request);
type HttpOption<T> = UseFetchOptions<ResOptions<T>, T, KeysOf<T>, any>;
interface ResOptions<T> {
data: T;
code: number;
message: boolean;
err?: string[];
}
function handleError<T>(
_method: string | undefined,
_response: FetchResponse<ResOptions<T>> & FetchResponse<any>,
) {
// Implement error handling logic here
console.error(`[useHttp] [error] ${_method}:`, _response);
}
function checkRef(obj: Record<string, any>) {
return Object.keys(obj).some(key => isRef(obj[key]));
}
function fetch<T>(url: UrlType, opts: HttpOption<T>): AsyncData<ResOptions<T>, FetchError<ResOptions<T>>> {
// Check the `key` option
const { key, params, watch } = opts;
if (!key && ((params && checkRef(params)) || (watch && checkRef(watch))))
console.error('\x1B[31m%s\x1B[0m %s', '[useHttp] [error]', 'The `key` option is required when `params` or `watch` has ref properties, please set a unique key for the current request.');
const options = opts as UseFetchOptions<ResOptions<T>>;
options.lazy = options.lazy ?? true;
const { baseUrl } = useRuntimeConfig().public;
return useFetch<ResOptions<T>>(url, {
// Request interception
onRequest({ options }) {
options.baseURL = baseUrl;
// Set the base URL
},
// Response interception
onResponse(_context) {
// Handle the response
},
// Error interception
onResponseError({ response, options: { method } }) {
handleError<T>(method, response);
},
// Set the cache key
key: key ?? hash(['api-fetch', url, JSON.stringify({ method: options.method, params: options.params })]),
// Merge the options
...options,
}) as AsyncData<ResOptions<T>, FetchError<ResOptions<T>>>;
}
export const useHttp = {
get: <T>(url: UrlType, params?: SearchParameters, option?: HttpOption<T>) => {
return fetch<T>(url, { method: 'get', params, ...option });
},
post: <T>(url: UrlType, body?: RequestInit['body'] | Record<string, any>, option?: HttpOption<T>) => {
return fetch<T>(url, { method: 'post', body, ...option });
},
put: <T>(url: UrlType, body?: RequestInit['body'] | Record<string, any>, option?: HttpOption<T>) => {
return fetch<T>(url, { method: 'put', body, ...option });
},
delete: <T>(url: UrlType, body?: RequestInit['body'] | Record<string, any>, option?: HttpOption<T>) => {
return fetch<T>(url, { method: 'delete', body, ...option });
},
};这里我们使用到了ohash这个库,你也可以不使用自己定义,如果你和我一样的话你可以安装一下这个库
npm i ohash
即可
代码分析
type UrlType = string | Request | Ref<string | Request> | (() => string | Request);
type HttpOption<T> = UseFetchOptions<ResOptions<T>, T, KeysOf<T>, any>;
interface ResOptions<T> {
data: T;
code: number;
message: boolean;
err?: string[];
}UrlType定义了fetch函数中 URL 参数的类型,可以是字符串、Request 对象、引用(Ref)或返回上述类型的函数。HttpOption<T>是对UseFetchOptions的扩展,用于配置 HTTP 请求选项。ResOptions<T>是响应对象的结构,包含 data、code、message和可选的 err字段。 这些类型字段是直接复制官方的源码的,官方ts类型体操实在是太复杂了,就直接复制粘贴了,ResOptions这个类型工具接口返回的数据格式做调整即可,这里只是写了一个示例。
function handleError<T>(
_method: string | undefined,
_response: FetchResponse<ResOptions<T>> & FetchResponse<any>,
) {
// Implement error handling logic here
console.error(`[useHttp] [error] ${_method}:`, _response);
}- handleError 函数用于处理请求错误,当前仅在控制台输出错误信息。实际使用中,可以在这里添加更多的错误处理逻辑。
- checkRef 是用于判断对象中是否包含 ref 对象
- 然后检查 key 是否存在,如果 params 或 watch 包含引用属性但没有 key,则输出错误信息
- key 是一个唯一的键,用于确保数据获取可以在请求之间正确去重。如果未提供,将根据使用useAsyncData的静态代码位置生成。
- 接着我们将 lazy 选项默认值改为了 true,避免页面切换时的阻塞,这个要根据实际去改掉,这个是解决页面长时间白屏的问题。
- 设置基础 URL 和请求头,其中包含当前语言环境。
- 使用 useFetch 函数发送请求,处理请求和响应拦截,并生成缓存键。
- 返回 AsyncData 对象,包含请求状态和数据。 最终,我们通过封装的 fetch 定义了一个 useHttp 可组合项,包含 get、post、put、delete 方法
api封装示例
interface List{
id: number;
name: string;
}
interface List{
id: number;
name: string;
}
export const getList = (params?: SearchParameters, option?: HttpOption<List>) => {
return useHttp.get<List>('/list', params, option);
}在代码中去使用
<script setup lang="ts">
const {data} = await getList()
</script>
<template>
<div>
<UButton label="Open"/>
<div>
data: {{ data}}
</div>
</div>
</template>本文标题:Nuxt3 useFetch封装最佳实践
本文链接:https://blog.luola.me/35.html
除非另有说明,本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议。
声明:转载请注明文章来源。
nice。 和用户交互的部分,还是要再封一个 $fetch ... ? 今天看了核心成员的 youtube 视频里推荐在 plugins 下搞个 api.ts,创建一个全局的 fetch。 不过 useFetch 的部分用你这个就挺好OωO