前言
nuxt3 已经发布了第一个正式版本,项目想迁移到nuxt3肯定避免不了踩坑,所以我把自己项目需要用到的组件都写一个小demo确定全部都可以实现之后再进行迁移,可以查看我的示例项目.
nuxt3 使用fetch作为默认的数据请求方式,官网介绍的使用方式没有使用拦截器的示例,这里简单贴一下fetch拦截器的实现.
更新: nuxt3.10之后直接使用useFetch封装会出现警告,[nuxt] [useFetch] Component is already mounted, please use $fetch instead. See https://nuxt.com/docs/getting-started/data-fetching
,最佳实践是建议使用$fetch
封装,useFetch
和useAsyncData
只在setup顶层使用,函数内部直接使用$fetch
封装的方法.示例项目已更新为nuxt3.10之后的最佳实践.
实现http
composables/useHttp.ts
import { Message } from '@arco-design/web-vue'
import type { FetchResponse, SearchParameters } from 'ofetch'
import { useUserStore } from '~/stores/user.store'
import IconEmoticonDead from '~icons/mdi/emoticon-dead'
export interface ResOptions<T> {
data: T
code: number
message: string
success: boolean
}
const handleError = <T>(response: FetchResponse<ResOptions<T>> & FetchResponse<ResponseType>) => {
const err = (text: string) => {
Message.error({
content: response?._data?.message ?? text,
icon: () => h(IconEmoticonDead),
})
}
if (!response._data) {
err('请求超时,服务器无响应!')
return
}
const userStore = useUserStore()
const handleMap: { [key: number]: () => void } = {
404: () => err('服务器资源不存在'),
500: () => err('服务器内部错误'),
403: () => err('没有权限访问该资源'),
401: () => {
err('登录状态已过期,需要重新登录')
userStore.clearUserInfo()
// TODO 跳转实际登录页
navigateTo('/')
},
}
handleMap[response.status] ? handleMap[response.status]() : err('未知错误!')
}
// get方法传递数组形式参数
const paramsSerializer = (params?: SearchParameters) => {
if (!params)
return
const query = useCloneDeep(params)
Object.entries(query).forEach(([key, val]) => {
if (typeof val === 'object' && Array.isArray(val) && val !== null) {
query[`${key}[]`] = toRaw(val).map((v: any) => JSON.stringify(v))
delete query[key]
}
})
return query
}
const fetch = $fetch.create({
// 请求拦截器
onRequest({ options }) {
// get方法传递数组形式参数
options.params = paramsSerializer(options.params)
// 添加baseURL,nuxt3环境变量要从useRuntimeConfig里面取
const { public: { apiBase } } = useRuntimeConfig()
options.baseURL = apiBase
// 添加请求头,没登录不携带token
const userStore = useUserStore()
if (!userStore.isLogin)
return
options.headers = new Headers(options.headers)
options.headers.set('Authorization', `Bearer ${userStore.getToken}`)
},
// 响应拦截
onResponse({ response }) {
if (response.headers.get('content-disposition') && response.status === 200)
return response
// 在这里判断错误
if (response._data.code !== 200) {
handleError(response)
return Promise.reject(response._data)
}
// 成功返回
return response._data
},
// 错误处理
onResponseError({ response }) {
handleError(response)
return Promise.reject(response?._data ?? null)
},
})
// 自动导出
export const useHttp = {
get: <T>(url: string, params?: any) => {
return fetch<T>(url, { method: 'get', params })
},
post: <T>(url: string, body?: any) => {
return fetch<T>(url, { method: 'post', body })
},
put: <T>(url: string, body?: any) => {
return fetch<T>(url, { method: 'put', body })
},
delete: <T>(url: string, body?: any) => {
return fetch<T>(url, { method: 'delete', body })
},
}
统一管理api
apis/index.ts
import * as login from './login'
export default {
login,
}
这里直接使用useHttp,composables文件夹下会自动导入
apis/login.ts
enum Api {
login = '/users/app/login',
logout = '/users/app/logout',
getUserInfo = '/users/app/getUserInfo',
}
export async function login(params: LoginParams) {
return useHttp.post<LoginResultModel>(Api.login, params)
}
export async function logout() {
return useHttp.post<void>(Api.logout)
}
export async function getUserInfo() {
return useHttp.get(Api.getUserInfo)
}
导出api
composables/index.ts
import api from '@/apis/index'
export const useApi = () => api
页面使用
useApi 直接使用,已经在composables/index.ts
导出
<script lang="ts" setup>
// 顶层请求使用useAsyncData
// 进入页面服务端请求数据,这种方式跟直接封装useFetch使用效果一样,更加灵活.
const { data, pending, refresh } = await useAsyncData(() => sys.getBanner(6), { lazy: true })
const bannerList = computed(() => {
return data.value?.data
})
// 函数内部,和中间件等不是setup顶层调用,不能使用useAsyncData包裹,直接调用
const login = async (params: LoginParams) => {
const { login } = useApi()
const { data } = await login.login(params)
userInfo.value = data
return data
}
</script>
完结
其他更多示例可以查看示例项目