Nuxt3提供了4种方式使得我们可以异步获取数据
- useAsyncData
- useLazyAsyncData (useAsyncData+lazy:true)
- useFetch
- useLazyFetch (useFetch+lazy:true)
4种方式中,其实核心的就是useAsyncData
和useFetch
。这两个方法不同于Nuxt2中的asyncData
和fetch
。接下来我们先来好好分析下这两个方法。
useAsyncData
我们知道,在Nuxt2中,asyncData
方法类似于一个生命周期函数,它在服务端或路由更新之前被调用。方法的参数是当前页面的上下文对象,我们一般是利用 asyncData
方法来获取数据并返回给当前组件,以避免请求放在客户端执行时带来的数据延迟出现问题。
export default {
data() {
return { project: 'default' }
},
asyncData(context) {
return { project: 'nuxt' }
}
}
在Nuxt3中,useAsyncData
可以看做是异步获取数据场景的一个封装,而且变成了一个主动调用函数,原则上可以在任何时机调用。
// 用法
const {
data: Ref<DataT>,// 返回的数据结果
pending: Ref<boolean>,// 是否在请求状态中
refresh: (force?: boolean) => Promise<void>,// 强制刷新数据
error?: any // 请求失败返回的错误信息
} = useAsyncData(
key: string, // 唯一键,确保相同的请求数据的获取和去重
fn: () => Object,// 一个返回数值的异步函数
options?: { lazy: boolean, server: boolean }
// options.lazy,是否在加载路由后才请求该异步方法,默认为false
// options.server,是否在服务端请求数据,默认为true
// options.default,异步请求前设置数据data默认值的工厂函数(对lazy:true选项特别有用)
// options.transform,更改fn返回结果的函数
// options.pick,只从数组中指定的key进行缓存
)
从api的设计中可以看出,useAsyncData
没有限制我们发起网络请求的方式,同时它还暴露了请求状态,增加了刷新控制,以及对重复获取数据的去重控制等。
使用示例如下:
<script setup>
const { data } = await useAsyncData('count', () => $fetch('/api/count'))
</script>
<template>
Page visits: {{ data }}
</template>
useFetch
在Nuxt2中,fetch 方法用于在渲染页面前填充应用的状态树(store)数据, 与 asyncData 方法类似,不同的是它不会设置组件的数据。
<template>
<h1>Stars: {{ $store.state.stars }}</h1>
</template>
<script>
export default {
fetch({ store, params }) {
return axios.get('http://my-api/stars').then(res => {
store.commit('setStars', res.data)
})
}
}
</script>
在Nuxt3中,useFetch
实际上是对useAsyncData
和$fetch
的封装,提供了一个更便捷的封装方法。它相比useAsyncData
, 主要做了以下两点处理:
- 它会根据URL和fetch参数自动生成一个key,同时推断出API的响应类型。也就是说不用手动指定key了。
它实现了网络请求的具体方式,使用
$fetch
发起请求,也就是说不需要再手动去实现网络请求的逻辑了。//useFetch用法 const { data: Ref<DataT>, pending: Ref<boolean>, refresh: (force?: boolean) => Promise<void>, error?: any } = useFetch(url: string, options?) // options (继承自 unjs/ofetch options以及 AsyncDataOptions) // 下边的这些参数是useAsyncData的options中没有的 // options.method: 请求方式 // options.query: url路径参数 // options.params: query参数的别名 // options.body: 请求体参数, // options.headers: 请求头的配置 // options.baseURL: 请求的基础Url地址
实战应用
我们不难发现,
useFetch
已经具备了网络请求的所有核心功能,虽然该Api主要用于在服务端请求,但它也是做了客户端请求的支持的,只要稍加封装改动,就可以同时用于服务端请求和客户端请求的场景。这样我们也就不用额外再引入像Axios
这样的请求库了。场景1: 如何处理对于带错误码的数据响应
通常我们的接口都不是直接返回数据,而是带了一个错误码和错误信息的对象,比如这样:
// response: { data: {age: 1}, code: 1 }
在这样的返回结构下,
useFetch
拿到的数据并不是我们真实想要的数据const { data } = await useFetch('/api/user/info', { method: 'get' }) console.log(data) // 此时data是一个Ref包裹的对象{ data: {age: 1}, code: 1 } const userInfo = unref(data).data //获取真正的数据需要先unref后再去获取data
所以,我们希望能在接口返回时对数据做一下转换,这里其实
useFetch
提供了相关的option参数,我们可以这样修改const { data: userInfo} = await useFetch('/api/user/info', { method: 'get', // 处理方式1 onResponse({ response }) { response._data = { ...response._data.data, } }, // 处理方式2 // transform: (res) => { // return res.data // }, })
场景2: 如何只在客户端侧发起请求
这样的场景一般用于使用静态化构建部署,但是页面上有些内容是不能在构建时静态化的。这时可以利用
server:false
参数// 异步获取当前用户信息 const { data: userinfo } = await useMyFetch('/api/auth/userinfo', { server: false })
注意: 这种情况下,如果想在script内直接获取到userinfo的内部值,是获取不到的!官方文档也做了对应的说明:
if you have not fetched data on the server (for example, with
server: false
), then the data will not be fetched until hydration completes. This means even if you await useFetch on client-side, data will remain null within<script setup>
.
如果非要在script中获取数据呢?这里笔者想到两个方案:
- 用
$fetch
去发起请求 场景3: 如何将请求结果转为非响应式的数据
这种场景一般用于在客户端发起的请求,我们不需要在页面上渲染响应的数据,只是为了做一些逻辑判断或者需要对数据进行加工。而
useFetch
请求后的返回值默认都是一个ref对象,我们得先获取内部值。const { data } = await useMyFetch('/api/get-actiocn-token') // data是一个Ref包裹的对象,需要用unref获取内部值 const tokenInfo = unref(data)
如果想直接获取原始数据的话,
useFetch
原生是不支持的(或者是我现在还不知道怎么实现)。我们只能使用$fetch
去实现了。
请求统一封装
针对上述三种场景,笔者分享下自己的封装思路,即在composables
目录中实现一个useMyFetch方法,去处理一些通用的逻辑
import type { NitroFetchRequest } from 'nitropack'
import type { FetchOptions, FetchResponse } from 'ofetch'
import type { UseFetchOptions } from 'nuxt/dist/app/composables/fetch'
function transFormResponse({ response }: any) {
// 处理后端携带了错误码响应的数据
if (response._data && response._data.code)
return Promise.reject(response._data)
response._data = {
...response._data.data,
}
}
/**
* 封装$fetch用于简单请求场景
* @param request
* @param opts
* @returns
*/
export function useClientFetch(request: NitroFetchRequest, opts?: FetchOptions<any>) {
return $fetch<FetchResponse<any>>(request, {
onResponse: transFormResponse,
...opts,
})
}
/**
* 抽离useFetch的通用配置
* @param request
* @param opts
* @returns
*/
export function useMyFetch(request: NitroFetchRequest, opts?: UseFetchOptions<any>) {
return useFetch(request, {
onResponse: transFormResponse,
...opts,
})
}
/**
* 实现更便捷的post请求
* @param request
* @param opts
* @returns
*/
useMyFetch.get = (request: NitroFetchRequest, opts?: UseFetchOptions<any>) => {
return useMyFetch(request, {
method: 'get',
...opts,
})
}
/**
* 实现更便捷的post请求
* @param request
* @param opts
* @returns
*/
useMyFetch.post = (request: NitroFetchRequest, opts?: UseFetchOptions<any>) => {
return useMyFetch(request, {
method: 'post',
...opts,
})
}
结语
博客原创地址:Nuxt3实战系列之网络请求篇
联系作者:imwty2023(微信),iwhitney@163.com(邮箱)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。