$axiosは廃止されたようなので、Fetch APIに書き換えます。少ない修正量を目指します。
nuxt-lodashも必要になったので、ここで導入しておきます。
前回:Nuxt BridgeをNuxt3に移行。vee-validateを導入
Using the Fetch API – Web APIs | MDN
- メソッドを作成・変更する
- 呼び出し元を書き換える
- サーバー入力エラーで、値を変更しても、ボタンが押せない(validにならない)
nuxt-lodashを導入- 【次回】injectを書き換える。utilsとの違い
- 今回のコミット内容
メソッドを作成・変更する
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って事なのかも。
It is a convention to name composable functions with camelCase names that start with "use". コンポーズ可能な関数には、「use」で始まるキャメルケース名を付けるのが慣例です。
【次回】injectを書き換える。utilsとの違い
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件のコメントがあります。