$axiosは廃止されたようなので、Fetch APIに書き換えます。少ない修正量を目指します。
nuxt-lodashも必要になったので、ここで導入しておきます。
前回:Nuxt BridgeをNuxt3に移行。vee-validateを導入

Using the Fetch API – Web APIs | MDN

メソッドを作成・変更する

composables/apiRequest.ts

composablesにuseApiRequestを作成して、呼び出し元の修正量を減らす事にしました。

元々、plugins/axios.jsで認証ヘッダを追加・保存していましたのを、ここに実装。
data(ボディ部分)はresponseに含まれるものの、受信完了を待つ必要があるので、ここで.json()で取得してセットしてます。

レスポンスヘッダの取得方法が変わっています。
 $axios: response.headers[‘xxx’]
 Fetch API: response.headers.get(‘xxx’)

FormData(ファイル等)で、Content-typeを指定するとエラーになります。

// APIリクエスト
export const useApiRequest = async (url: string, method = 'GET', params: any = null, type: string | null = 'json', accept: string | null = 'application/json') => {
  const $config = useRuntimeConfig()
  /* c8 ignore next */ // eslint-disable-next-line no-console
  if ($config.public.debug) { console.log('useApiRequest', method, url) }

  // Devise Token Auth
  let authHeaders = {}
  if (localStorage.getItem('token-type') === 'Bearer') {
    const reqAccessToken = localStorage.getItem('access-token')
    if (reqAccessToken != null) {
      authHeaders = {
        uid: localStorage.getItem('uid'),
        client: localStorage.getItem('client'),
        'access-token': reqAccessToken
      }
    }
  }

  let contentType = {}
  let urlParam = ''
  let body: any = null
  if (method === 'GET') {
    if (params != null) { urlParam = '?' + new URLSearchParams(params) }
  } else if (type === 'json') {
    contentType = { 'Content-type': 'application/json; charset=utf-8' }
    if (params != null) {
      body = JSON.stringify(params)
    }
  } else { // 'form'
    // eslint-disable-next-line no-lonely-if
    if (params != null) {
      body = new FormData()
      for (const [key, value] of Object.entries(params)) {
        body.append(key, value)
      }
    }
  }

  let acceptHeader = {}
  if (accept != null) {
    acceptHeader = { Accept: accept }
  }

  let response: any = null
  try {
    response = await fetch(url + urlParam, {
      method,
      mode: 'cors',
      headers: {
        ...contentType,
        ...acceptHeader,
        ...authHeaders
      },
      body
    })
  } catch (error) {
    /* c8 ignore next */ // eslint-disable-next-line no-console
    if ($config.public.debug) { console.log(error) }
  }

  // Devise Token Auth
  if (response != null) {
    const resTokenType = response.headers.get('token-type')
    const resAccessToken = response.headers.get('access-token') || ''
    if (resTokenType === 'Bearer' && resAccessToken !== '') {
      localStorage.setItem('token-type', resTokenType)
      localStorage.setItem('uid', response.headers.get('uid'))
      localStorage.setItem('client', response.headers.get('client'))
      localStorage.setItem('access-token', resAccessToken)
      localStorage.setItem('expiry', response.headers.get('expiry'))
    }
  }

  let data: any = null
  try {
    if (response != null) {
      if (accept === 'application/json') {
        data = await response.json()
      } else {
        data = await response.blob()
      }
    }
  } catch (error) {
    /* c8 ignore next */ // eslint-disable-next-line no-console
    if ($config.public.debug) { console.log(error) }
  }

  /* c8 ignore next */ // eslint-disable-next-line no-console
  if ($config.public.debug) { console.log(response, data) }

  return [response, data]
}

utils/application.js

    // レスポンスチェック
-    appCheckResponse (response, action = { redirect: false, toasted: false, returnKey: false }, systemError = false) {
+    appCheckResponse (data, action = { redirect: false, toasted: false, returnKey: false }, systemError = false) {
-      if (response.data == null || systemError) {
+      if (data == null || systemError) {
        return this.appReturnResponse(action, null, 'system.error')
      }

      return (action.returnKey) ? null : true
    },

response.data -> data

    // エラーレスポンスチェック
-    appCheckErrorResponse (error, action = { redirect: false, toasted: false, returnKey: false, require: false }, check = { auth: false, forbidden: false, notfound: false, reserved: false }) {
+    appCheckErrorResponse (status, data, action = { redirect: false, toasted: false, returnKey: false, require: false }, check = { auth: false, forbidden: false, notfound: false, reserved: false }) {
-      if (.response == null) {
+      if (status == null && data == null) {
        return this.appReturnResponse(action, null, 'network.failure')
-      } else if (check.auth && error.response.status === 401) {
+      } else if (check.auth && status === 401) {
        if (this.$auth.loggedIn) {
          this.appSignOut()
        } else if (action.redirect) {
-          this.appRedirectSignIn({ alert: this.appGetAlertMessage(error.response.data, action.require, 'auth.unauthenticated'), notice: error.response.data?.notice })
+          this.appRedirectSignIn({ alert: this.appGetAlertMessage(data, action.require, 'auth.unauthenticated'), notice: data?.notice })
        } else if (action.toasted) {
-          this.appSetToastedMessage(error.response.data, action.require, 'auth.unauthenticated')
+          this.appSetToastedMessage(data, action.require, 'auth.unauthenticated')
        }
        return (action.returnKey) ? 'auth.unauthenticated' : false
-      } else if (check.forbidden && error.response.status === 403) {
+      } else if (check.forbidden && status === 403) {
-        return this.appReturnResponse(action, error.response.status, 'auth.forbidden')
+        return this.appReturnResponse(action, status, 'auth.forbidden')
-      } else if (check.notfound && error.response.status === 404) {
+      } else if (check.notfound && status === 404) {
-        return this.appReturnResponse(action, error.response.status, 'system.notfound', error.response.data)
+        return this.appReturnResponse(action, status, 'system.notfound', data)
-      } else if (check.reserved && error.response.status === 406) {
+      } else if (check.reserved && status === 406) {
-        return this.appReturnResponse(action, error.response.status, 'auth.destroy_reserved', error.response.data)
+        return this.appReturnResponse(action, status, 'auth.destroy_reserved', data)
-      } else if (error.response.data == null) {
+      } else if (data == null) {
-        return this.appReturnResponse(action, error.response.status, 'network.error')
+        return this.appReturnResponse(action, status, 'network.error')
      }

      if (action.require) {
-        return this.appReturnResponse(action, error.response.status, 'system.default', error.response.data)
+        return this.appReturnResponse(action, status, 'system.default', data)
      }

      return (action.returnKey) ? null : true
    },

-        this.appCheckErrorResponse(error, { toasted: true, require: true })
+        this.appCheckErrorResponse(null, error, { toasted: true, require: true })

error.response.data -> data(= await response.json())
error.response.status -> status(= response.status)

意外と変更量は多くなってしまいましたが、行数はあまり変わらないかな。

呼び出し元を書き換える

GETの例: components/index/Infomations.vue

-      await this.$axios.get(this.$config.public.apiBaseURL + this.$config.public.infomations.importantUrl)
+      const [response, data] = await useApiRequest(this.$config.public.apiBaseURL + this.$config.public.infomations.importantUrl)
+
-        .then((response) => {
+      if (response?.ok) {
-          this.errorMessage = this.appCheckResponse(response, { returnKey: true })
+        this.errorMessage = this.appCheckResponse(data, { returnKey: true })
-          if (this.errorMessage != null) { return }
+        if (this.errorMessage != null) { return }

-          this.infomations = response.data.infomations
+        this.infomations = data.infomations
-        },
-        (error) => {
+      } else {
-          this.errorMessage = this.appCheckErrorResponse(error, { returnKey: true, require: true })
+        this.errorMessage = this.appCheckErrorResponse(response?.status, data, { returnKey: true, require: true })
-        })
+      }

POST(json)の例: pages/users/sign_up.vue

-      await this.$axios.post(this.$config.public.apiBaseURL + this.$config.public.singUpUrl, {
+      const [response, data] = await useApiRequest(this.$config.public.apiBaseURL + this.$config.public.singUpUrl, 'POST', {
        ...this.query,
        confirm_success_url: this.$config.public.frontBaseURL + this.$config.public.singUpSuccessUrl
      })

POST(params)の例: components/users/update/Image.vue

v-file-inputの値が配列に変更になっていたので、image[0]に変更しました。

-      await this.$axios.post(this.$config.public.apiBaseURL + this.$config.public.userImageUpdateUrl, params)
+      const [response, data] = await useApiRequest(this.$config.public.apiBaseURL + this.$config.public.userImageUpdateUrl, 'POST', {
+        image: this.image[0]
+      }, 'form')

$axiosでは、500や404等、通信エラーで例外が発生しましたが、
Fetch APIでは通信エラーは例外になりますが、500や404等は例外にならない。
なので、response?.okで分岐するようにしました。
response.data -> data

サーバー入力エラーで、値を変更しても、ボタンが押せない(validにならない)

setErrorsの仕様が変わったようで、Railsだとfull_messagesが含まれるので、入力し直してもvalidがtrueにならない。ボタンが押せない。

なので、これでもOKだけど。。。

-          this.$refs.form.setErrors(data.errors)
+          this.$refs.form.setErrors({ ...data.errors, full_messages: null })

そもそも存在しない項目があると解除できないので、valuesにあるのだけ残すように変更。
valuesはslotから取得。序でにsetErrorsもslotから取得するように変更します。
ただ長いので。。。

-        <Form v-slot="{ meta }" ref="form">
+        <Form v-slot="{ meta, setErrors, values }">

-                @click="postSingUp()"
+                @click="postSingUp(setErrors, values)"

-    async postSingUp () {
+    async postSingUp (setErrors, values) {

-          this.$refs.form.setErrors({ ...data.errors, full_messages: null })
+          let errors = {}
+          for (const [key, value] of Object.entries(data.errors)) {
+            if (values[key] != null) { errors[key] = value }
+          }
+          setErrors(errors) // NOTE: 未使用の値があるとvalidがtrueに戻らない為

オブジェクト(配列ではない)だとfilter使えないので、自前で実装して入れ直してみましたが、
lodashのpickBy使えば、絞り込めるので、こちらに変えます。nuxt-lodashを導入が必要。

+ import { pickBy } from 'lodash'

-          let errors = {}
-          for (const [key, value] of Object.entries(data.errors)) {
-            if (values[key] != null) { errors[key] = value }
-          }
-          this.$refs.form.setErrors(errors) // NOTE: 未使用の値があるとvalidがtrueに戻らない為
+          setErrors(pickBy(data.errors, (_value, key) => values[key] != null)) // NOTE: 未使用の値があるとvalidがtrueに戻らない為

nuxt-lodashを導入

これ使えば、毎回importしなくても良くなる。
Lodash Module · Nuxt
-> MIT license

例えば、pickByの場合、usePickByと書けば使えると。
useがちょっと曖昧だけど、デフォルトに揃えたいのでそのままにしました。設定で変更可能。

ただ、一部使えなかったり、test書く時にそれぞれにstub経由で動くようにするのが手間だったので、普通にlodashを入れてimportするように変更しました。
Lodash
-> MIT license

% yarn add -D nuxt-lodash
% yarn add -D lodash @types/lodash

nuxt.config.ts

  modules: [
    '@nuxtjs/i18n',
+    'nuxt-lodash'

composablesなのでuseって事なのかも。

Composables | Vue.js

It is a convention to name composable functions
 with camelCase names that start with "use".
コンポーズ可能な関数には、「use」で始まるキャメルケース名を付けるのが慣例です。

APIリクエストして、vee-validateが正しく動作する所まで行けました。
次回は、下記の対応から記載します。
Nuxt BridgeをNuxt3に移行。injectを書き換える。utilsとの違い

今回のコミット内容

origin#507 Fetch APIに書き換え、nuxt-lodashを導入、setErrorsを修正
origin#507 MixinのappApiRequestをuseApiRequest.tsに変更
origin#507 レスポンスヘッダ取得方法変更($axios→Fetch API)
origin#507 v-text-fieldで初期値が表示されない。v-file-inputがエラーになる。useApiRequestのFormData対応
origin#507 Messageが再表示されない。useApiRequest拡張。リファクタ。デザイン調整
origin#507 nuxt-lodashをlodashに変更、Vuetifyの設定をVitestと共通化、リファクタ

Nuxt BridgeをNuxt3に移行。$axiosをFetch APIに書き換える。nuxt-lodashを導入” に対して1件のコメントがあります。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です