Skip to content

useFetch

Category
Export Size
2.24 kB
Last Changed
3 months ago

响应式 Fetch API 提供了中止请求、在请求发送前拦截请求、URL 变化时自动重新获取请求以及使用预定义选项创建你自己的 useFetch 的能力。

通过 Vue School 的免费视频课程学习 useFetch!

TIP

与 Nuxt 3 一起使用时,此函数将不会被自动导入,以支持 Nuxt 内置的 useFetch()。如果你想使用 VueUse 中的函数,请显式导入。

Demo

The following URLs can be used to test different features of useFetch
Normal Request: https://httpbin.org/get
Abort Request: https://httpbin.org/delay/10
Response Error: http://httpbin.org/status/500
isFinished: false
isFetching: false
canAbort: false
statusCode: null
error: null
data: null

用法

基本用法

useFetch 函数可以通过简单地提供一个 URL 来使用。URL 可以是字符串或 refdata 对象将包含请求的结果,error 对象将包含任何错误,isFetching 对象将指示请求是否正在加载。

ts
import { useFetch } from '@vueuse/core'

const { isFetching, error, data } = useFetch(url)

异步用法

useFetch 也可以像普通的 fetch 一样被 await。请注意,当一个组件是异步的时,任何使用它的组件都必须将该组件包装在一个 <Suspense> 标签中。你可以在 Vue 3 官方文档中阅读更多关于 suspense API 的信息。

ts
import { useFetch } from '@vueuse/core'

const { isFetching, error, data } = await useFetch(url)

URL 变化时重新获取

对 url 参数使用 ref 将允许 useFetch 函数在 url 更改时自动触发另一个请求。

ts
const url = ref('https://my-api.com/user/1')

const { data } = useFetch(url, { refetch: true })

url.value = 'https://my-api.com/user/2' // 将触发另一个请求

阻止请求立即触发

immediate 选项设置为 false 将阻止请求在 execute 函数被调用之前触发。

ts
const { execute } = useFetch(url, { immediate: false })

execute()

中止请求

可以使用 useFetch 函数中的 abort 函数中止请求。canAbort 属性指示请求是否可以中止。

ts
const { abort, canAbort } = useFetch(url)

setTimeout(() => {
  if (canAbort.value)
    abort()
}, 100)

也可以通过使用 timeout 属性自动中止请求。当达到给定的超时时间时,它将调用 abort 函数。

ts
const { data } = useFetch(url, { timeout: 100 })

拦截请求

beforeFetch 选项可以在请求发送前拦截它,并修改请求选项和 URL。

ts
const { data } = useFetch(url, {
  async beforeFetch({ url, options, cancel }) {
    const myToken = await getMyToken()

    if (!myToken)
      cancel()

    options.headers = {
      ...options.headers,
      Authorization: `Bearer ${myToken}`,
    }

    return {
      options,
    }
  },
})

afterFetch 选项可以在响应数据更新前拦截它。

ts
const { data } = useFetch(url, {
  afterFetch(ctx) {
    if (ctx.data.title === 'HxH')
      ctx.data.title = 'Hunter x Hunter' // 修改响应数据

    return ctx
  },
})

updateDataOnError 设置为 true 时,onFetchError 选项可以在响应数据和错误更新前拦截它们。

ts
const { data } = useFetch(url, {
  updateDataOnError: true,
  onFetchError(ctx) {
    // 当出现 5xx 响应时,ctx.data 可能为 null
    if (ctx.data === null)
      ctx.data = { title: 'Hunter x Hunter' } // 修改响应数据

    ctx.error = new Error('自定义错误') // 修改错误
    return ctx
  },
})

console.log(data.value) // { title: 'Hunter x Hunter' }

设置请求方法和返回类型

可以通过在 useFetch 末尾添加适当的方法来设置请求方法和返回类型。

ts
// 请求将以 GET 方法发送,数据将解析为 JSON
const { data } = useFetch(url).get().json()

// 请求将以 POST 方法发送,数据将解析为文本
const { data } = useFetch(url).post().text()

// 或者使用选项设置方法

// 请求将以 GET 方法发送,数据将解析为 blob
const { data } = useFetch(url, { method: 'GET' }, { refetch: true }).blob()

创建自定义实例

createFetch 函数将返回一个 useFetch 函数,其中包含提供给它的任何预配置选项。这对于在整个应用程序中与使用相同基本 URL 或需要授权标头的 API 进行交互非常有用。

ts
const useMyFetch = createFetch({
  baseUrl: 'https://my-api.com',
  options: {
    async beforeFetch({ options }) {
      const myToken = await getMyToken()
      options.headers.Authorization = `Bearer ${myToken}`

      return { options }
    },
  },
  fetchOptions: {
    mode: 'cors',
  },
})

const { isFetching, error, data } = useMyFetch('users')

如果你想控制预配置实例和新生成的实例之间的 beforeFetchafterFetchonFetchError 的行为,你可以提供一个 combination 选项来在 overwritechaining 之间切换。

ts
const useMyFetch = createFetch({
  baseUrl: 'https://my-api.com',
  combination: 'overwrite', // 'overwrite' 或 'chaining'
  options: {
    // 仅当新生成的实例未传递 beforeFetch 时,预配置实例中的 beforeFetch 才会运行
    async beforeFetch({ options }) {
      const myToken = await getMyToken()
      options.headers.Authorization = `Bearer ${myToken}`

      return { options }
    },
  },
})

// 使用 useMyFetch 的 beforeFetch
const { isFetching, error, data } = useMyFetch('users')

// 使用自定义的 beforeFetch
const { isFetching, error, data } = useMyFetch('users', {
  async beforeFetch({ url, options, cancel }) {
    const myToken = await getMyToken()

    if (!myToken)
      cancel()

    options.headers = {
      ...options.headers,
      Authorization: `Bearer ${myToken}`,
    }

    return {
      options,
    }
  },
})

你可以通过在 afterFetchonFetchError 中调用 execute 方法来重新执行请求。这是一个刷新令牌的简单示例:

ts
let isRefreshing = false
const refreshSubscribers: Array<() => void> = []

const useMyFetch = createFetch({
  baseUrl: 'https://my-api.com',
  options: {
    async beforeFetch({ options }) {
      const myToken = await getMyToken()
      options.headers.Authorization = `Bearer ${myToken}`

      return { options }
    },
    afterFetch({ data, response, context, execute }) {
      if (needRefreshToken) { // 假设 needRefreshToken 是一个判断是否需要刷新 token 的条件
        if (!isRefreshing) {
          isRefreshing = true
          refreshToken().then((newToken) => {
            if (newToken.value) {
              isRefreshing = false
              setMyToken(newToken.value) // 假设 setMyToken 是设置新 token 的函数
              onRrefreshed()
            }
            else {
              refreshSubscribers.length = 0
              // 处理刷新 token 错误
            }
          })
        }

        return new Promise((resolve) => {
          addRefreshSubscriber(() => {
            execute().then((response) => {
              resolve({ data, response })
            })
          })
        })
      }

      return { data, response }
    },
    // 或者使用 onFetchError 和 updateDataOnError
    updateDataOnError: true,
    onFetchError({ error, data, response, context, execute }) {
      // 与 afterFetch 类似
      return { error, data }
    },
  },
  fetchOptions: {
    mode: 'cors',
  },
})

async function refreshToken() {
  const { data, execute } = useFetch<string>('refresh-token', { // 假设 'refresh-token' 是刷新 token 的接口
    immediate: false,
  })

  await execute()
  return data
}

function onRrefreshed() {
  refreshSubscribers.forEach(callback => callback())
  refreshSubscribers.length = 0
}

function addRefreshSubscriber(callback: () => void) {
  refreshSubscribers.push(callback)
}

const { isFetching, error, data } = useMyFetch('users')
js
let isRefreshing = false
const refreshSubscribers = []
const useMyFetch = createFetch({
  baseUrl: 'https://my-api.com',
  options: {
    async beforeFetch({ options }) {
      const myToken = await getMyToken()
      options.headers.Authorization = `Bearer ${myToken}`
      return { options }
    },
    afterFetch({ data, response, context, execute }) {
      if (needRefreshToken) {
        // 假设 needRefreshToken 是一个判断是否需要刷新 token 的条件
        if (!isRefreshing) {
          isRefreshing = true
          refreshToken().then((newToken) => {
            if (newToken.value) {
              isRefreshing = false
              setMyToken(newToken.value) // 假设 setMyToken 是设置新 token 的函数
              onRrefreshed()
            } else {
              refreshSubscribers.length = 0
              // 处理刷新 token 错误
            }
          })
        }
        return new Promise((resolve) => {
          addRefreshSubscriber(() => {
            execute().then((response) => {
              resolve({ data, response })
            })
          })
        })
      }
      return { data, response }
    },
    // 或者使用 onFetchError 和 updateDataOnError
    updateDataOnError: true,
    onFetchError({ error, data, response, context, execute }) {
      // 与 afterFetch 类似
      return { error, data }
    },
  },
  fetchOptions: {
    mode: 'cors',
  },
})
async function refreshToken() {
  const { data, execute } = useFetch('refresh-token', {
    // 假设 'refresh-token' 是刷新 token 的接口
    immediate: false,
  })
  await execute()
  return data
}
function onRrefreshed() {
  refreshSubscribers.forEach((callback) => callback())
  refreshSubscribers.length = 0
}
function addRefreshSubscriber(callback) {
  refreshSubscribers.push(callback)
}
const { isFetching, error, data } = useMyFetch('users')

事件

onFetchResponseonFetchError 将分别在 fetch 请求响应和错误时触发。

ts
const { onFetchResponse, onFetchError } = useFetch(url)

onFetchResponse((response) => {
  console.log(response.status)
})

onFetchError((error) => {
  console.error(error.message)
})

Type Declarations

Show Type Declarations
typescript
export interface UseFetchReturn<T> {
  /**
   * Indicates if the fetch request has finished
   */
  isFinished: Readonly<ShallowRef<boolean>>
  /**
   * The statusCode of the HTTP fetch response
   */
  statusCode: ShallowRef<number | null>
  /**
   * The raw response of the fetch response
   */
  response: ShallowRef<Response | null>
  /**
   * Any fetch errors that may have occurred
   */
  error: ShallowRef<any>
  /**
   * The fetch response body on success, may either be JSON or text
   */
  data: ShallowRef<T | null>
  /**
   * Indicates if the request is currently being fetched.
   */
  isFetching: Readonly<ShallowRef<boolean>>
  /**
   * Indicates if the fetch request is able to be aborted
   */
  canAbort: ComputedRef<boolean>
  /**
   * Indicates if the fetch request was aborted
   */
  aborted: ShallowRef<boolean>
  /**
   * Abort the fetch request
   */
  abort: Fn
  /**
   * Manually call the fetch
   * (default not throwing error)
   */
  execute: (throwOnFailed?: boolean) => Promise<any>
  /**
   * Fires after the fetch request has finished
   */
  onFetchResponse: EventHookOn<Response>
  /**
   * Fires after a fetch request error
   */
  onFetchError: EventHookOn
  /**
   * Fires after a fetch has completed
   */
  onFetchFinally: EventHookOn
  get: () => UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>
  post: (
    payload?: MaybeRefOrGetter<unknown>,
    type?: string,
  ) => UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>
  put: (
    payload?: MaybeRefOrGetter<unknown>,
    type?: string,
  ) => UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>
  delete: (
    payload?: MaybeRefOrGetter<unknown>,
    type?: string,
  ) => UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>
  patch: (
    payload?: MaybeRefOrGetter<unknown>,
    type?: string,
  ) => UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>
  head: (
    payload?: MaybeRefOrGetter<unknown>,
    type?: string,
  ) => UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>
  options: (
    payload?: MaybeRefOrGetter<unknown>,
    type?: string,
  ) => UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>
  json: <JSON = any>() => UseFetchReturn<JSON> &
    PromiseLike<UseFetchReturn<JSON>>
  text: () => UseFetchReturn<string> & PromiseLike<UseFetchReturn<string>>
  blob: () => UseFetchReturn<Blob> & PromiseLike<UseFetchReturn<Blob>>
  arrayBuffer: () => UseFetchReturn<ArrayBuffer> &
    PromiseLike<UseFetchReturn<ArrayBuffer>>
  formData: () => UseFetchReturn<FormData> &
    PromiseLike<UseFetchReturn<FormData>>
}
type Combination = "overwrite" | "chain"
export interface BeforeFetchContext {
  /**
   * The computed url of the current request
   */
  url: string
  /**
   * The request options of the current request
   */
  options: RequestInit
  /**
   * Cancels the current request
   */
  cancel: Fn
}
export interface AfterFetchContext<T = any> {
  response: Response
  data: T | null
  context: BeforeFetchContext
  execute: (throwOnFailed?: boolean) => Promise<any>
}
export interface OnFetchErrorContext<T = any, E = any> {
  error: E
  data: T | null
  response: Response | null
  context: BeforeFetchContext
  execute: (throwOnFailed?: boolean) => Promise<any>
}
export interface UseFetchOptions {
  /**
   * Fetch function
   */
  fetch?: typeof window.fetch
  /**
   * Will automatically run fetch when `useFetch` is used
   *
   * @default true
   */
  immediate?: boolean
  /**
   * Will automatically refetch when:
   * - the URL is changed if the URL is a ref
   * - the payload is changed if the payload is a ref
   *
   * @default false
   */
  refetch?: MaybeRefOrGetter<boolean>
  /**
   * Initial data before the request finished
   *
   * @default null
   */
  initialData?: any
  /**
   * Timeout for abort request after number of millisecond
   * `0` means use browser default
   *
   * @default 0
   */
  timeout?: number
  /**
   * Allow update the `data` ref when fetch error whenever provided, or mutated in the `onFetchError` callback
   *
   * @default false
   */
  updateDataOnError?: boolean
  /**
   * Will run immediately before the fetch request is dispatched
   */
  beforeFetch?: (
    ctx: BeforeFetchContext,
  ) =>
    | Promise<Partial<BeforeFetchContext> | void>
    | Partial<BeforeFetchContext>
    | void
  /**
   * Will run immediately after the fetch request is returned.
   * Runs after any 2xx response
   */
  afterFetch?: (
    ctx: AfterFetchContext,
  ) => Promise<Partial<AfterFetchContext>> | Partial<AfterFetchContext>
  /**
   * Will run immediately after the fetch request is returned.
   * Runs after any 4xx and 5xx response
   */
  onFetchError?: (
    ctx: OnFetchErrorContext,
  ) => Promise<Partial<OnFetchErrorContext>> | Partial<OnFetchErrorContext>
}
export interface CreateFetchOptions {
  /**
   * The base URL that will be prefixed to all urls unless urls are absolute
   */
  baseUrl?: MaybeRefOrGetter<string>
  /**
   * Determine the inherit behavior for beforeFetch, afterFetch, onFetchError
   * @default 'chain'
   */
  combination?: Combination
  /**
   * Default Options for the useFetch function
   */
  options?: UseFetchOptions
  /**
   * Options for the fetch request
   */
  fetchOptions?: RequestInit
}
export declare function createFetch(
  config?: CreateFetchOptions,
): typeof useFetch
export declare function useFetch<T>(
  url: MaybeRefOrGetter<string>,
): UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>
export declare function useFetch<T>(
  url: MaybeRefOrGetter<string>,
  useFetchOptions: UseFetchOptions,
): UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>
export declare function useFetch<T>(
  url: MaybeRefOrGetter<string>,
  options: RequestInit,
  useFetchOptions?: UseFetchOptions,
): UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>

Source

SourceDemoDocs

Contributors

Anthony Fu
wheat
Anthony Fu
Jelf
Ismail Gjevori
IlyaL
qiang
Robin
丶远方
青椒肉丝
KaKa
Toby Zerner
Jay214
webfansplz
Arthur Darkstone
James Garbutt
Robin
Gergely Dremák
mrchar
Pouya Mohammadkhani
BaboonKing
LJFloor
mymx2
Arthur Machado
Martijn Weghorst
KaKa
RAX7
Przemek Brzosko
abitwhy
sun0day
Young
sun0day
Curt Grimes
Yvan Zhu
ice
Antonio Román
Glandos
unknown_
btea
Shinigami
KaKa
Arda Soytürk

Changelog

7432f - feat(types): deprecate MaybeRef and MaybeRefOrGetter in favor of Vue's native (#4636)
3ca0d - fix: partial overwrite when {combination: 'overwrite'} (#4430)
98a83 - feat: add parameters to the afterFetch and onFetchError (#4499)
59f75 - feat(toValue): deprecate toValue from @vueuse/shared in favor of Vue's native
8a89d - fix: handle empty payload (#4366)
0a9ed - feat!: drop Vue 2 support, optimize bundles and clean up (#4349)
3d29c - feat: infer 'json' type for array payloads (#4329)
3de68 - fix: ensure single slash (#4296)
f5587 - fix: remove unnecessary spread operator in iterable conversion (#3660)
31d4a - fix: mark isFinished, isFetching readonly (#3616)
a086e - fix: stricter types
fccf2 - feat: upgrade deps (#3614)
8cbfd - fix: clone 'Response' on reading (#3607) (#3608)
3456d - fix: immediately modify the status after the request is completed (#3603)
75ca2 - fix: don't set isFetching to false when a request is aborted because of a refetch (#3479)
945ca - feat: introduce updateDataOnError option (#3092)
b7e3d - fix: generated payloadType on execute (#3251)
d051f - fix: combineCallbacks does not merge options (#3015)
4d757 - feat(types)!: rename MaybeComputedRef to MaybeRefOrGetter
10e98 - feat(toRef)!: rename resolveRef to toRef
0a72b - feat(toValue): rename resolveUnref to toValue

Released under the MIT License.