Loading... ### 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封装最佳实践 ```ts 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这个库,你也可以不使用自己定义,如果你和我一样的话你可以安装一下这个库 <div class="tip inlineBlock share"> npm i ohash </div> 即可 ### 代码分析 ```ts 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这个类型工具接口返回的数据格式做调整即可,这里只是写了一个示例。 ```ts 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封装示例 ```ts 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); } ``` 在代码中去使用 ```ts <script setup lang="ts"> const {data} = await getList() </script> <template> <div> <UButton label="Open"/> <div> data: {{ data}} </div> </div> </template> ``` 最后修改:2024 年 08 月 27 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏