NUXT3 fetch拦截器简单封装

9,627 阅读2分钟

image.png

前言

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封装,useFetchuseAsyncData只在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>

完结

其他更多示例可以查看示例项目