$axiosは廃止されたようなので、Fetch APIに書き換えます。少ない修正量を目指します。
nuxt-lodashも必要になったので、ここで導入しておきます。
前回:Nuxt BridgeをNuxt3に移行。vee-validateを導入
Using the Fetch API – Web APIs | MDN
- 呼び出し元を書き換える
- メソッドを作成・変更する
- サーバー入力エラーで、値を変更しても、ボタンが押せない(validにならない)
- nuxt-lodashを導入
- 【次回】injectを書き換える。utilsとの違い
- 今回のコミット内容
呼び出し元を書き換える
appApiRequestを作成して、呼び出し元の修正量を減らす事にしました。
TODO: 一旦、Mixinを使ったので、Mixin止める時に修正する予定です。
composablesにuseApiRequestを作成して、呼び出し元の修正量を減らす事にしました。
GETの例: components/index/Infomations.vue
- await this.$axios.get(this.$config.public.apiBaseURL + this.$config.public.infomations.importantUrl)+ const [response, data] = await this.appApiRequest(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
※FormData(ファイル等)で、Content-typeを指定するとエラーになるので修正しました。
序でにbodyの整形もここに移動しました。
※JSON.stringify()は共通化の為、useApiRequestに移動しました。
- await this.$axios.post(this.$config.public.apiBaseURL + this.$config.public.singUpUrl, {+ const [response, data] = await this.appApiRequest(this.$config.public.apiBaseURL + this.$config.public.singUpUrl, 'POST', JSON.stringify({+ const [response, data] = await useApiRequest(this.$config.public.apiBaseURL + this.$config.public.singUpUrl, 'POST', + JSON.stringify({+ 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
※FormData()は共通化の為、useApiRequestに移動しました。
また、v-file-inputの値が配列に変更になっていたので、image[0]に変更しました。
const params = new FormData() params.append('image', this.image)- await this.$axios.post(this.$config.public.apiBaseURL + this.$config.public.userImageUpdateUrl, params)+ const [response, data] = await this.appApiRequest(this.$config.public.apiBaseURL + this.$config.public.userImageUpdateUrl, 'POST', params)+ const [response, data] = await useApiRequest(this.$config.public.apiBaseURL + this.$config.public.userImageUpdateUrl, 'POST', + 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
メソッドを作成・変更する
utils/application.js
composables/apiRequest.ts
+ // APIリクエスト + async appApiRequest (url, method = 'GET', body = null) { + // Devise Token Auth + let authHeaders = {} + if (localStorage.getItem('token-type') === 'Bearer' && localStorage.getItem('access-token')) { + authHeaders = { + uid: localStorage.getItem('uid'), + client: localStorage.getItem('client'), + 'access-token': localStorage.getItem('access-token') + } + } + + let response = null + try { + response = await fetch(url, { + method, + mode: 'cors', + headers: { + 'Content-type': 'application/json; charset=utf-8', + Accept: 'application/json', + ...authHeaders + }, + body + }) + } catch (error) { + // eslint-disable-next-line no-console + if (this.$config.public.debug) { console.log(error) } + } + + // Devise Token Auth + if (response?.headers['token-type'] === 'Bearer' && response?.headers['access-token']) { + localStorage.setItem('token-type', response.headers['token-type']) + localStorage.setItem('uid', response.headers.uid) + localStorage.setItem('client', response.headers.client) + localStorage.setItem('access-token', response.headers['access-token']) + localStorage.setItem('expiry', response.headers.expiry) + } + + let data = null + try { + if (response != null) { data = await response.json() } + } catch (error) { + // eslint-disable-next-line no-console + if (this.$config.public.debug) { console.log(error) } + } + + return [response, data] + },
※レスポンスヘッダの取得方法が間違っていたので修正しました。
$axios: response.headers[‘xxx’]
Fetch API: response.headers.get(‘xxx’)
※FormData(ファイル等)で、Content-typeを指定するとエラーになるので修正しました。
序でにbodyの整形もここに移動しました。
// APIリクエストexport const useApiRequest = async (url: string, method = 'GET', body: any = null) => {export const useApiRequest = async (url: string, method = 'GET', params: object | null = null, type = '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' && localStorage.getItem('access-token')) { authHeaders = { uid: localStorage.getItem('uid'), client: localStorage.getItem('client'), 'access-token': localStorage.getItem('access-token')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 body: any = null 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 response: any = null try { response = await fetch(url, { method, mode: 'cors', headers: {'Content-type': 'application/json; charset=utf-8',...contentType, Accept: 'application/json', ...authHeaders }, body }) } catch (error) { /* c8 ignore next */ // eslint-disable-next-line no-consoleconst $config = useRuntimeConfig()if ($config.public.debug) { console.log(error) } } // Devise Token Authif (response?.headers['token-type'] === 'Bearer' && response?.headers['access-token']) { localStorage.setItem('token-type', response.headers['token-type']) localStorage.setItem('uid', response.headers.uid) localStorage.setItem('client', response.headers.client) localStorage.setItem('access-token', response.headers['access-token']) localStorage.setItem('expiry', response.headers.expiry)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) { data = await response.json() } } catch (error) { /* c8 ignore next */ // eslint-disable-next-line no-consoleconst $config = useRuntimeConfig()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] }
元々、plugins/axios.jsで認証ヘッダを追加・保存していましたのを、ここに実装。
data(ボディ部分)はresponseに含まれるものの、受信完了を待つ必要があるので、ここで.json()で取得してセットしてます。
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)
意外と変更量は多くなってしまいましたが、行数はあまり変わらないかな。
サーバー入力エラーで、値を変更しても、ボタンが押せない(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を導入が必要。
- 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(usePickBy(data.errors, (_value, key) => values[key] != null)) // NOTE: 未使用の値があるとvalidがtrueに戻らない為
nuxt-lodashを導入
これ使えば、毎回importしなくても良くなる。
Lodash Module · Nuxt
-> MIT license
例えば、pickByの場合、usePickByと書けば使えると。
useがちょっと曖昧だけど、デフォルトに揃えたいのでそのままにしました。設定で変更可能。
% yarn add -D nuxt-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対応
“Nuxt BridgeをNuxt3に移行。$axiosをFetch APIに書き換える。nuxt-lodashを導入” に対して1件のコメントがあります。