Nuxt3實戰系列之網路請求篇

imwty發表於2023-02-18

Nuxt3提供了4種方式使得我們可以非同步獲取資料

  • useAsyncData
  • useLazyAsyncData (useAsyncData+lazy:true)
  • useFetch
  • useLazyFetch (useFetch+lazy:true)

4種方式中,其實核心的就是useAsyncDatauseFetch。這兩個方法不同於Nuxt2中的asyncDatafetch。接下來我們先來好好分析下這兩個方法。

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, 主要做了以下兩點處理:

  1. 它會根據URL和fetch引數自動生成一個key,同時推斷出API的響應型別。也就是說不用手動指定key了。
  2. 它實現了網路請求的具體方式,使用$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中獲取資料呢?這裡筆者想到兩個方案:

  1. $fetch去發起請求
  2. watch監聽userinfo的值變化

    場景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實戰系列之網路請求篇

聯絡作者:whitney1289(微信),iwhitney@163.com(郵箱)

相關文章