Skip to content

useFetch

Category
Export Size
2.24 kB
Last Changed
6 minutes 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

jojo

Changelog

No recent changes

Released under the MIT License.