- 投稿日:2021-01-08T23:50:31+09:00
Vue+Vuex+Vue router+AxiosによるAPIエラーハンドリングとページ遷移の実装パターン
この記事について
- Vueコンポーネント内やVuexのAction内などあちこちでAxiosによるAPIリクエストをしていると、エラーハンドリングを個別に記述しなければならなくなり、DRYではなくなってしまう
- APIリクエストでエラーが発生した場合に特定のページに遷移させたいのに、Vueインスタンスの外からは$router.push()が呼べない(例えばVuex内から)
この記事では、上記のよくある問題をなるべくDRYかつシンプルに解決した方法を述べます。
環境
- Vue2系(Options API)
- Vuex
- Vue Router
- JavaScript
APIリクエストを1箇所で管理する
参考:https://kntmr.hatenablog.com/entry/2018/02/28/200112
各所でAxiosをそのまま用いて非同期通信をすると、それぞれでエラーをcatchしなければなりません。それは辛いので、別にモジュールを用意してAxiosをラップした関数を定義し、その中でエラーハンドリングを行います。基本的に参考サイトのままですが、関数の部分だけ、get, postなどメソッドで分けずに、configで指定するようにしています。
/src/api/index.jsimport axios from 'axios' const debug = process.env.NODE_ENV !== 'production' const onSuccess = (resp) => { if (debug) { console.log(' << ' + JSON.stringify(resp.data)) } return Promise.resolve(resp.data) } const onError = () => { throw new Error('API error.') } // リクエストメソッド問わず同じ関数を用いる const api = (config) => { if (debug) { console.log(`${config.method} ${config.url} >> ${config.params}`); } return axios(config) .then(onSuccess) .catch(onError); }; export default api;コンポーネントでaxios()を使っていた箇所をapi()に置き換えることで、エラーハンドリングの記述が1箇所・1回で済みます、イッツDRY。
エラー時にVue Routerでページ遷移
上記まででエラーハンドリングは1箇所で管理することが出来ました。
さてその際、APIアクセスが失敗した時に、Vue Routerの特定にページに遷移させたい時があります。なので、上記の
/api/index.js
のonError()
で、$router.push()したい訳です。ですがコンポーネントの外(=Vueインスタンスの外)から$router.push()は使うことが出来ません。なのでちょっと工夫します。
Vueインスタンスのexport
Vueプロジェクトのエントリーポイントである
/src/main.js
は、Vue CLIでプロジェクトを生成した際は下記のようにVueインスタンスを生成・HTMLを描画しています。/src/main.js// 略 new Vue({ render: h => h(App), }).$mount('#app')ここで、Vueインスタンスをエクスポートします。
/src/main.js// 略 const vm = new Vue({ render: h => h(App), }).$mount('#app') export default vm;ここでexportしている
vm
は、Vueインスタンスのシングルトンです。どこでimportしても常に同一のインスタンスを参照します。参考:https://qiita.com/NeGI1009/items/f8b17d856a4b15b1ecbc
なので
/src/main.js
をimportすればどこからでもVueインスタンスにアクセス出来る訳です。とはいえ、様々な場所で無秩序にVueインスタンスを操作するのは避けるべきと考えます。
apiモジュールでVueインスタンスにアクセス
/src/api/index.jsimport axios from 'axios' // Vueインスタンス import vm from '../main'; //略 const onError = () => { //エラー時にルートへ遷移 vm.$router.push('/') throw new Error('API error.') } //略この時、Vuex APIにもRouterと同様、vm.$storeでアクセス出来ます。
以上でAPIリクエスト時のエラーハンドリングの集中管理およびエラー時のページ遷移が実現しました。あちこちに散逸しがちなエラーハンドリングに対する悪くない実装パターンではないでしょうか。
- 投稿日:2021-01-08T23:50:31+09:00
Vue+Vue router+AxiosによるAPIエラーハンドリングとページ遷移の実装パターン
この記事について
- Vueコンポーネント内やVuexのAction内などあちこちでAxiosによるAPIリクエストをしていると、エラーハンドリングを個別に記述しなければならなくなり、DRYではなくなってしまう
- APIリクエストでエラーが発生した場合に特定のページに遷移させたいのに、Vueインスタンスの外からは$router.push()が呼べない
この記事では、上記のよくある問題をなるべくDRYかつシンプルに解決した方法を述べます。
検証環境
- Vue2系(Options API、でもcomposition APIでも動くと思う)
- Vuex(今回の実装には関係ないけれど、Vuexを組み合わせる事も出来る)
- Vue Router
- JavaScript
APIリクエストを1箇所で管理する
参考:https://kntmr.hatenablog.com/entry/2018/02/28/200112
各所でAxiosをそのまま用いて非同期通信をすると、それぞれでエラーをcatchしなければなりません。それは辛いので、別にモジュールを用意してAxiosをラップした関数を定義し、その中でエラーハンドリングを行います。基本的に参考サイトのままですが、関数の部分だけ、get, postなどメソッドで分けずに、configで指定するようにしています。
/src/api/index.jsimport axios from 'axios' const debug = process.env.NODE_ENV !== 'production' const onSuccess = (resp) => { if (debug) { console.log(' << ' + JSON.stringify(resp.data)) } return Promise.resolve(resp.data) } const onError = () => { throw new Error('API error.') } // リクエストメソッド問わず同じ関数を用いる const api = (config) => { if (debug) { console.log(`${config.method} ${config.url} >> ${config.params}`); } return axios(config) .then(onSuccess) .catch(onError); }; export default api;コンポーネントやVuexでaxios()を使っていた箇所をapi()に置き換えることで、エラーハンドリングの記述が1箇所・1回で済みます、イッツDRY。
エラー時にVue Routerでページ遷移
上記まででエラーハンドリングは1箇所で管理することが出来ました。
さてその際、APIアクセスが失敗した時に、Vue Routerの特定にページに遷移させたい時があります。なので、上記の
/src/api/index.js
のonError()
で、$router.push()したい訳です。ですがコンポーネントの外(=Vueインスタンスの外)から$router.push()は使うことが出来ません。なのでちょっと工夫します。
Vueインスタンスのexport
Vueプロジェクトのエントリーポイントである
/src/main.js
は、Vue CLIでプロジェクトを生成した際は下記のようにVueインスタンスを生成・HTMLを描画しています。/src/main.js// 略 new Vue({ render: h => h(App), }).$mount('#app')ここで、Vueインスタンスをエクスポートします。
/src/main.js// 略 const vm = new Vue({ render: h => h(App), }).$mount('#app') export default vm;ここでexportしている
vm
は、Vueインスタンスのシングルトンです。どこでimportしても常に同一のインスタンスを参照します。参考:https://qiita.com/NeGI1009/items/f8b17d856a4b15b1ecbc
なので
/src/main.js
をimportすればどこからでもVueインスタンスにアクセス出来る訳です。とはいえ、様々な場所で無秩序にVueインスタンスを操作するのは避けるべきと考えます。
apiモジュールでVueインスタンスにアクセス
/src/api/index.jsimport axios from 'axios' // Vueインスタンス import vm from '../main'; //略 const onError = () => { //エラー時にルートへ遷移 vm.$router.push('/') throw new Error('API error.') } //略この時、Vuex APIにもRouterと同様、vm.$storeでアクセス出来ます。つまりVuexに保存した変数を取得出来るし(getter)、setterやactionを発火させる事も出来ます。
以上でAPIリクエスト時のエラーハンドリングの集中管理およびエラー時のページ遷移が実現しました。あちこちに散逸しがちなエラーハンドリングに対する悪くない実装パターンではないでしょうか。
- 投稿日:2021-01-08T17:45:29+09:00
【 JavaScript 】Async/awaitとpromiseの非同期処理で詰まった話
初投稿です。忘備録としてQiitaに投稿します。
VueとLIFFでLINEからuser_idを取得し、user_idをHTTPパラメータとしてaxiosでHTTPS通信を行おうとしたのですが、詰まったので記録として残しておきます。
起きた問題
user_idをパラメータとして入力しているはずなのに、パラメータ部がundefinedとなっていて通信できない。
promiseでの非同期処理コード
created(){ liff.init({ liffId: this.myLiffId }).then(() => { ・・・ } liff.getProfile().then((response) => { ・・・ } }(汚いコードですいません)
クソ雑魚プログラマの自分にはなぜうまく行かないか分からなかったのですが、先輩に聞いたところthenでPromiseチェーンを作った場合、
同期的処理になるのはPromiseチェーンの中だけで、外部は非同期的に処理されるらしいです。
(ここもあまり理解できていないので間違っていた場合はご指摘ください)そのため、vueのtemplete内の処理は非同期的に処理されるため、user_idがまだ取得されていない状態でtemplete処理が起こるようです。
解決法
Promiseチェーンではなく、async/awaitではuser_idが取得されてからtemplete処理が行われるようになるため、async/awaitで非同期処理を行います。
async/awaitでの非同期処理コード
async created() { await liff.init({ liffId: this.myLiffId }); ・・・ await liff.getProfile().then((response) => { ・・・ });user_idが取得されてからHTTPSリクエストが行われるようになりました。
Vueは専門ではないのに解決策を提示していただいた先輩には頭が上がりません。参考にさせていただいた記事
- 投稿日:2021-01-08T17:42:47+09:00
Vue.js + Rails で電子年賀状アプリを作った話
背景
自分は季節の行事や習慣が好きでそれを自分の得意なことで表現したいということから年賀状のWebサイトを作ろうと考えました。当初は自分がWebサイトを作ってそれを年賀状として近しい人に見てもらおうと考えていましたが、年賀状を作れるWebアプリの方が面白いなと考え、誰でも年賀状を作成できるアプリに変更しました。
PCユーザーをメインで作成しました。
しかし、大半の人がスマホで遊んでくれたことでスマホのバグがたくさん見つかり、年末はバグと戦いましたが、今ではスマホでも難なく遊べるはずです。。使用技術
- Vue.js・・・フロントエンド
- Rails・・・バックエンド
- FirebaseAuth・・・ログイン認証
- TwitterAPI・・・年賀状の公開範囲の設定
↓↓↓デプロイも済ませておりますので、ぜひ遊んでみて下さい↓↓↓
https://newyearmaker.netlify.app/card/new開発環境
- macOS 10.15.7
- Ruby 2.7.2
- Rails 6.0.3
- vue/cli 4.5.6
軽くデモ
1. まずは年賀状作成ページから
- 年賀状の動く背景を選択
- 年賀状に乗せるメッセージの入力
- 年賀状の公開範囲の設定
2. 次に年賀状ページ
- anime.jsによるアニメーション
- 限定公開の場合ページを読み込む際に見ているユーザー情報から公開制限を行っている
- 左下のボタンから年賀状の受け取り
- 右下のボタンから年賀状作成ページへ
3. 最後にユーザーページ
- 年賀状ページから受け取った年賀状一覧を表示、クリックすることで年賀状を確認することが可能
- 自分が作った年賀状の編集ページへ
工夫したところ
誰でも気軽に使ってもらいたかったので、ログインしなくても遊べるようにしました(ログインしない場合は機能が制限されてしまいますが)。
自分の周りでTwitterを使っている人が多かったので、フォロワー限定公開などの機能を実装しました。firebaseを用いてTwitterでのログインを可能にしており、実装がとても簡単でした。
ユーザーページは12/31にひらめいて必死に作ってたらガキ使が終わってました。。。(笑)感想
Railsはチュートリアル程度の知識しかなかったのですが、とても参考になる記事が多く実装が思ったより早くできました。
Vueは半年とちょっとしか触っていませんが、オフラインのハッカソンに二回ほど参加させていただき、そこで多くのものを学ぶことができ、効率的に学習できたと思います。ハッカソンを開いていただいた方々、教えて下さったメンターの方々、そこで出会った先輩に感謝しかありません。最後に
自分が作った年賀状よかったら見てみて下さい。↓↓↓
https://newyearmaker.netlify.app/card/b5d4cc19/show以上、ここまで読んでいただきありがとうございます。
年賀状の背景動画を作ってくれたYさんありがとう!!
- 投稿日:2021-01-08T13:00:35+09:00
laravelで画像を配列で保存し、vue.jsで表示
ポートフォリオでinstagramのような写真共有のアプリを作る際に初心者の俺はとりあえず思いついたアイデアで写真の保存と表示をやってみた。
今後改善していく予定なので、現在の仕様をとりあえず記念に掲載。post.vue<template> <div> <input ref="input" type="file" multiple @input="load"> </div> <div> <button @click="submit">送信</button> </div> </template> <script> export default { data() { return { base64Images: '', } }, methods: { load(){ const files = this.$refs.input.files;//ref="input"の要素のfiles var uploadedBase64Images = [];//複数のimageのアップロードを想定し、格納する配列を準備 for (var i = 0; i < files.length; i++) {//for文をfilesの数だけ回すためにfiles.length const reader = new FileReader(); let that = this; reader.onload = (function (event) { postArray.unshift(event.target.result);//unshiftは先頭にデータを入れる。pushは末尾 }); reader.readAsDataURL(files[i]); } this.base64Images = postArray; //https://qiita.com/popo62520908/items/d707ff2010be78f97a50 でreaderの復習 }, submit(){ let fd= new FormData();//fileを使うため fd.append("images", JSON.stringify(this.base64Images));//postを使うときは、JSON.stringify()を使って配列を文字列に axios.post('/api/create', fd) .then( response => (window.location.href = '/')//処理が完了後homeに戻す ) .catch(function (error) { console.log(error); }); }, } } </script>
formでfileを含む時のContent-Typeは、multipart/form-dataになるので、new FormData()を使います(fileは文字、画像など様々な種類のデータを取り扱うため)。
とどこかのサイトに書いてあり、postはJSON.stringfy()で文字列に変換しなければと書いてあったのだが、以下のやり方でもいけた。post.vue<script> export default { data() { return { base64Images: '', } }, methods: { submit(){ var param = { images: this.base64Images, } axios.post('/api/posts', param) .then( response => (window.location.href = '/')//処理が完了後homeに戻す ) .catch(function (error) { console.log(error); }); }, } } </script>次にサーバー側のデータ受け取り、保存。
PostController<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Post; use Illuminate\Support\Facades\Storage; class PostController extends Controller { public function index() { return view('post'); } public function create(Request $request) { $post = new Post;//インスタンス作成 $decoded_images = json_decode($request->images);//文字列化されたjsonを配列に戻す。 $src_array = [];//この中に保存するファイル名を格納し、データベースに保存する。 for ($i=0; $i<count($decoded_images); $i++) {//vueで作ったload()関数と似た感じの処理。 $base64_image = $decoded_images[$i]; @list($type, $file_data[$i]) = explode(';', $base64_image);//explodeで$base64_imageないの文字列を分割し、list関数で分割したデータを変数$typeと$file_data[$i]に格納。 //つまりdata:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAA....が //$type = 'data:image/jpeg;'; //$file_data[$i] = 'base64,/9j/4AAQSkZJRgABAQEASABIAA....'; //ということ @list(, $file_data[$i]) = explode(',', $file_data[$i]); //更にこの処理によって //$file_data[$i] = 'base64,/9j/4AAQSkZJRgABAQEASABIAA....';が //$file_data[$i] = '/9j/4AAQSkZJRgABAQEASABIAA....'; //になる $imageName = str_random(10).'.'.'png';//$file_data[$i]の名前を作成。str_random()関数を作ってランダムに Storage::disk('local')->put($imageName, base64_decode($file_data[$i]));//diskは保存場所。詳しくは/config/filesystems.phpで設定。 //localに$imageNameという名前でbase64_decode($file_data[$i])のデータを保存しますということ。 //use Illuminate\Support\Facades\Storage;忘れずに $array[] = $imageName;//この$imageNameを配列化 $src_array += $array;//格納 } $post->image = json_encode($src_array);//$src_arrayは['RgABAQEASA.png','SkZJRgABAQ.png']こんな感じで、このままでは保存できないのでjson_encodeで文字列、"['RgABAQEASA.png','SkZJRgABAQ.png']"に変換。 $post->save();//そして保存 } }いい記事発見したので掲載
https://qiita.com/PlanetMeron/items/2905e2d0aa7fe46a36d4
list関数はこちらを
https://www.sejuku.net/blog/24406ちなみにJSの new FormDataを使わない場合
PostControllerpublic function create(Request $request) { $post = new Post; $post->save(); $decoded_images = $request->images; //$decoded_imagesも何も、最初から文字列にエンコードされていないのだが、以下の書き換えが面倒なので変数$decoded_imagesに格納 $src_array = []; for ($i=0; $i<count($decoded_images); $i++) { $base64_image = $decoded_images[$i]; @list($type, $file_data[$i]) = explode(';', $base64_image); @list(, $file_data[$i]) = explode(',', $file_data[$i]); $imageName = str_random(10).'.'.'png'; Storage::disk('local')->put($imageName, base64_decode($file_data[$i])); $array[] = $imageName; $src_array += $array; } $post->image = json_encode($src_array); $post->save(); }当時は配列ではなくオブジェクトで保存していた。
そもそも配列とオブジェクトの違いもわからなかった。
本当に懐かしい。笑show.vue<template> <div> <div v-for="(parsedUserPost, index) in parsedUserPosts"> <v-carousel> <v-carousel-item v-for="(image,i) in parsedUserPost.image" :key="i" :src="'storage/image/' + image"></v-carousel-item> </v-carousel> </div> </div> </template> <script> export default { data() { return { userPosts: '',//←propsおよびajaxでの値を受け取っている前提 } }, computed: { parsedUserPosts(){ var userPosts = this.userPosts; if(userPosts.length == 0){ return false;//this.userPostsがundefinedの歳エラーが出るためこの処理 } else { for(var i=0; i<userPosts.length; i++){ userPosts[i].image = JSON.parse(userPosts[i].image);//JSON.parse()を使って文字列だった配列をもとに戻す。 //"['RgABAQEASA.png','SkZJRgABAQ.png']"こんな感じで保存されているので、['RgABAQEASA.png','SkZJRgABAQ.png']に戻してあげる。 } return userPosts; } }, } } </script>JSON.parseを使っているが、コントローラーでjson_decode()を使っても行けるはず。
これだけですごい勉強になった気がするな。当時の思いつきで突き進んだ結果。
- 投稿日:2021-01-08T09:06:38+09:00
webpackを使って手動でコンパイルしたjsをrails6で読み込むことに成功
方針
railsでデフォルトで入っているgem webpackerを使わずにwebpackを使って手動でjsをコンパイルする。
entryファイル内でvue.jsを読み込み、componentを使ってアプリの見た目を作っていく。
webpackを使ってbuildされたjsをrailsアプリで読み込み、componentをアプリに読み込む。現状の実装内容
画像の通りindex.html.erbにcomponentで定義したh1要素を表示させることに成功
本日の実装の詳細
※最初にgitignoreが反映されていなかったので以下の手順にて修正
1.gitignoreを編集
2.以下のコマンドでcasheを削除
git rm -r --cached . //ファイル全体キャッシュ削除3.commit & push
いろいろろ設定をいじったらちゃんとwebpackでコンパイルしたjsを読み込めた
結論以下のことを行った
application.rbでassetsのコンパイル対象を変更
config.assets.paths << Rails.root.join("public/javascripts")assets.rbでjsとcssのコンパイル対象を増やす(application.rb書いても良さそう)
Rails.application.config.assets.precompile += %w(*.js *.css) Rails.application.config.assets.precompile << /(^[^_\/]|\/[^_])[^\/]*(\.js|\.css)$/manifest.jsでpublick/javascripts以下のファイルを読み込むようにする
//= link_directory ../../../public/javascripts .js
application.rbに記述しただけではpublic以下のファイルは読み込んでくれなさそう?な感じなので無理やりmanifest.jsで読み込むようにした。他にもやり方はありそうで、例えばwebpackを使ってコンパイルしたファイルをassets/javascritps内にbuildする方法とかもあるようだ。
で、これでrails sをすると…
無事にブラウザ表示できた。
しかし
Failed to mount component: template or render function not definedと出た
ファッ!?
見た感じVue.jsで作ったcomponentが読み込めていなさそう。ちょっと調べてみるか…
こんな記事にヒット
http://howdy.hatenablog.com/entry/2016/11/08/230439
どうやらresolveの設定が必要らしい。
ということでwebpack.config.jsに以下の記述を追加
resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1 } },これで無事解決!!!!componentで設定したHello!を読み込んでくれました!
Sidebar.vue
<template> <h1>Hello!</h1> </template> <script> </script>App.vue
<template> <div> <sidebar></sidebar> <chat-container></chat-container> </div> </template> <script> import Sidebar from './components/Sidebar.vue' import ChatContainer from './components/ChatContainer.vue' export default { components:{ Sidebar, ChatContainer } } </script>main.js
import Vue from 'vue'; import App from './App.vue'; // App.vueをエントリとしてレンダリング new Vue({ el: '#app', render: h => h(App) })index.html.erb
<div id="app"></div>いまいちなぜ解決したのか自分でも理解できていないので整理
When using vue-loader or vueify, templates inside *.vue files are pre-compiled into JavaScript at build time. You don’t really need the compiler in the final bundle, and can therefore use the runtime-only build. Since the runtime-only builds are roughly 30% lighter-weight than their full-build counterparts, you should use it whenever you can. If you still wish to use the full build instead, you need to configure an alias in your bundler:(参考:https://vuejs.org/v2/guide/installation.html#Runtime-Compiler-vs-Runtime-only)
この文章を見る限りvue-loaderを使っているときはruntime状態のファイルを使うことができるので完全にcompileしたファイルを使う必要がない。しかしそれでも完全にコンパイルされたファイルを使いたいのであればresolveの設定をする必要があります
と言っているように思う。そして今回私はvue-loaderを使っている。つまりvueファイルの読み込みの仕方が良くないのかもしれない?いまいちよくわからないが読み込めたのでOK。パフォーマンスの良し悪しとかはまた調べてみよう。
- 投稿日:2021-01-08T00:28:14+09:00
VueJSのData,Store,Propsについて
Vueを使った際の状態管理についてちょいと考えてみた。
Vueで状態管理というとData・Store(Vuex)・Propsがあるけど、
それらの使い所って結局何?みたいな感じになるかと思いますが、
私見満載な感じにサンプル踏まえてまとめてみました。
※なんか違うかなと思ったらご指摘・ご意見下さい。vueの公式マニュアルを順番に読み進めていくと、
サンプルとしてカウンターの話が出てくるかと思います。とっても簡単なcountを出すアレ<template> <div>{{count}}</div> </template> export default { data:() => ({ count: 0, }) }例えば
上のコンポーネントをAとした時に、
countの情報は「A(の中)だけでしか使わない」のであれば、
これはもうDataで良いと思います。
※だってどこにも公開しなくていい子だから。では、
このcountをコンポーネントBでも使いたいとき、
※AやBでカウントアップしたらどっちも同じ値が出るようにする場合を指します
この時は手法が2つあるのかなと思います。
- Storeでcountを管理する
AとBがそれぞれ独立したコンポーネント(例えば画面Aと画面Bみたいな)で、例えば画面Xでcountをインクリした時に画面Aに遷移したときと画面Bに遷移した時に同じ値が表示されていることを想定
storeの方const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } } })利用側(AもBも同じとする(コンポーネントとしては有りえんけど))<template> <div>{{$store.state.count}}</div> </template>これで、A/B以外のコンポーネントからincrementがcommitされると、どっちのコンポーネントもインクリされた同じ値が表示できるねという話。
- Propsでcountを管理(受け取る)する
A/Bを同時に利用している「親となるコンポーネント」のdataでcountを管理して、単に参照渡しする場合
propを使った場合のコンポーネントAまたはB<template> <div>{{count}}</div> </template> export default { props: { count: { type: Number, required: true }, } }親側<template> <div> <a-compo :count="count"></a-compo> <b-compo :count="count"></b-compo> </div> </template> // import文 省略 export default { components: { a-compo, b-compo, }, data:() => ({ count: 0, }) }※Propは状態管理?な感じもするけどとりあえず載せてみた
そんなわけで簡単にまとめると使い所としては
Data:対象コンポーネント内だけで使うデータ
Store:グローバルなデータ(例:ログインユーザ情報など)や親子関係じゃないコンポーネント間のデータ共有時
Props:親から子へのデータ渡しのときまぁあくまで私見なので1意見として流し読んでもらえると良いかなと。
※世の中には「Dataじゃなくて何でもStoreで管理しろ」とか言う方もいるみたいなので
- 投稿日:2021-01-08T00:02:15+09:00
Vue-Nativeで画像ファイルをmultipart/form-data形式でPostする
概要
Vue-Nativeアプリ上で選択した画像を、multipart/form-data形式でAPIにリクエストするまでをまとめたいと思います。
実践
画像ファイルを選択
vue.jsasync pickImage() { let result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.All, allowsEditing: true, quality: 1, }) store.dispatch("image/selectedImage", result) }開発支援サービスのExpo.ioを使用しているため、
expo-image-picker
で画像を取り扱います。オプションは公式リファレンス参照。
https://docs.expo.io/versions/latest/sdk/imagepicker/返り値(result)
Object { "cancelled": false, "height": 750, "type": "image", "uri": "file://~~~~.jpg", "width": 748, }プレビュー表示
<image class="image-cover" :source="{uri: image}" />プレビュー表示する場合は、上記の表記方法で
image
は取得した返り値をvuexで状態管理されたstateにcomputedで監視させて呼び出します。画像ファイルはアプリの起動中に一時的にキャッシュされているので、そのuriで読み込みます。APIに送信
async saveImage ({state, rootState, dispatch}) { const data = new FormData(); let extention = state.selectedImage.uri.split(".")[1] var mineType = await dispatch('mineType', extention) data.append('image', { uri: state.selectedImage.uri, name: String(rootState.auth.id) + '-image.' + extention, type: mineType } ) var config = { method: 'post', url: baseApiUrl + '/users/'+ String(rootState.auth.user.id) + '/image', headers: { Accept: "application/json", "Content-Type": "multipart/form-data", }, data: data, }; return axios(config) }, mineType ({}, extention) { let allow = {"png":"image/png","pdf":"image/gif","jpeg":"image/jpeg", "jpg":"image/jpg"}; if (allow[extention] !== undefined){ return allow[extention] } else { return undefined } }FormDataには上記の形式でアペンドします。postする際は、ヘッダーオプションに
Accept
が必要なので注意が必要です。参考資料
https://stackoverflow.com/questions/32441963/how-to-use-formdata-in-react-native