- 投稿日:2020-08-24T23:54:06+09:00
【Vuetify】メインコンテンツの背景色を変更する
赤枠部分の背景色を変更するのに地味にハマったのでメモ。
環境
Vuetify 2.3.9
変更方法
テーマをカスタマイズして
background
を設定する。vuetify.tsimport Vue from "vue"; import Vuetify from "vuetify/lib"; Vue.use(Vuetify); export default new Vuetify({ theme: { themes: { light: { background: "#f5f5f5" } } } });
v-app
にスタイルをバインドするApp.vue<v-app :style="{ background: $vuetify.theme.themes.light.background }"> <v-navigation-drawer app> <!-- --> </v-navigation-drawer> <v-app-bar app> <!-- --> </v-app-bar> <v-main> <v-container fluid> <router-view></router-view> </v-container> </v-main> <v-footer app> <!-- --> </v-footer> </v-app>参考
https://stackoverflow.com/questions/50243769/vuetify-how-to-set-background-color
https://vuetifyjs.com/ja/customization/theme/
- 投稿日:2020-08-24T22:40:17+09:00
【Vue.js】Vue.js devtoolsが表示出来ない時にみる
vue.js備忘録、第二弾。
誰かの参考になれば幸いです。Vue.js devtoolsとは
Vueのデバッグを行う際に非常に便利な拡張機能です。
コンポーネント構造を直感的操作で確認出来て、プロパティーの中身も見る事が出来ます。導入方法は非常に簡単でこちらからダウンロードしてくるだけです。
正しい動き
検証でデベロッパーツールを開き、Vueタブが表示されていればOK
Vueタブが表示されないパターン①
「ファイルの URL へのアクセスを許可する」がオフになっている場合はオンにしてみましょ。
Vueタブが表示されないパターン②
当たり前ですが、productionモードでは表示されないです。
開発モードで検証しましょう。
Vueタブが表示されないパターン③
ここまで見てもVueタブが出てきてくれなかったら、キャッシュを疑いましょう。
NetworkタブのDisable cacheにチェックを入れて再読み込みして改善するか確認しましょう。これで大体解決出来るはずですm(__)m
- 投稿日:2020-08-24T22:03:41+09:00
Rails * Vue.jsで多階層カテゴリを作った話
やったこと
Rails * Vue.js でアプリを作っています。
既存のRailsファイルに単一ファイルコンポーネントとしてVue.jsを組み込んでいくスタイルです。親カテゴリー > 子カテゴリー > 孫カテゴリー
というふうに、多階層になっているカテゴリーのUIを作りました。イメージとしては、別サイトからの引用ですが、こちらの画像のようなものです。
その時のコードを共有します。
前提条件
アプリの前提条件は次の通りです。
Rails 5.2.4.2
Vue.js 3.0.1
Rails * Vue.js * 単一ファイルコンポーネントの組み合わせで、Vue.jsを動かす方法はこちらをご覧ください。(こちらはいつかきちんとQiita記事にしたいです。。。)
Rails, Vue.js で単一ファイルコンポーネントを使用したいときの設定ファイルの書き方また、
ajax
(axios)を使った、RailsとVueのデータのやり取りの方法はこちらをご覧ください。
VueのコンポーネントにRailsのデータを使った話(SFC利用、Railsの一部にVue使用最後に、多階層カテゴリーの作成にあたっては
ancestory
というgemを利用しています。ancestory
の使い方はこちらをご覧ください。
多階層カテゴリでancestryを使ったら便利すぎた作成したコード
テンプレート
今回作成したコードはこちらです。まずはテンプレートから。
なお、わかりやすさのため、Railsにデータを送るために必要なname属性や、Railsによる自動生成のidの記載は省いています。javascript/components/select_form.vue<template> <div class="select-form"> <!-------- 親カテゴリー -------------> <div> <label>カテゴリー</label> <select @change="findChildren"> <option v-for="root in roots" :value="root.id" :key="root.id">{{ root.name }}</option> </select> </div> <!-------- 子カテゴリー -------------> <div> <select @change="findGrandChildren"> <option v-for="child in children" :value="child.id" :key="child.id">{{ child.name }}</option> </select> </div> <!-------- 孫カテゴリー -------------> <div> <select> <option v-for="gChild in grandChildren" :value="gChild.id" :key="gChild.id">{{ gChild.name }}</option> </select> </div> </div> </template> <script> // ajax通信を行うためにaxiosを読み込む import axios from 'axios'; export default { data: function() { return { roots: [], children: [], grandChildren: [], root_id: "", child_id: "" } }, mounted() { // DOM要素読み込み時に、それぞれのselectのオプションに初期値を入れる // response.data.roots などには、controllerで定義しjbuilderで生成した値が入る axios.get('/api/somethings/new.json').then(response => (this.roots = response.data.roots, this.children = response.data.children, this.grandChildren = response.data.grandChildren)) }, methods: { findChildren: function(event) { // selectボックスの値が変化したときに、optionのvalue値を取得する。以下同じ let rootValue = event.target.value; return this.root_id = rootValue; }, findGrandChildren: function(event) { let childValue = event.target.value; return this.child_id = childValue; } }, watch: { // root_id, child_idの値が変化したときに、新たなリクエストを生成する root_id: function() { if (this.root_id !== "" ) { //paramsでroot_idをcontrollerに渡す。 axios.get('/api/somethings/new.json', { params: { root_id: this.root_id } }).then(response => (this.children = response.data.children)) } }, child_id: function() { if (this.child_id !== "" ) { //孫カテゴリー生成時には、root_id, child_idをcontrollerに渡す axios.get('/api/somethings/new.json', { params: { root_id: this.root_id, child_id: this.child_id } }).then(response => (this.grandChildren = response.data.grandChildren)) } } } } </script>controller
コントローラーの定義は下記の通りです。実装にあたっては、こちらの記事を参考にしました。
[Rails] Ajax通信を用いたカテゴリボックス作成なぜ
/api/
を挟んだディレクトリ構造になっているかは、先に挙げたこちらの記事をご参照ください。controllers/api/somethings_controller.rbclass Api::SomethingsController < ApplicationController def new # 親カテゴリーの生成 @roots = Category.roots root_id = params[:root_id] child_id = params[:child_id] # paramsにroot_idがあれば子カテゴリーを生成、なければ空の配列を返す。子と孫も同様。 @children = root_id ? @roots.find(root_id).children : [] @grand_children = child_id ? @children.find(child_id).children : [] render 'new', formats: 'json', handlers: 'jbuilder' end endjbuilder
上記の
controller
で定義したデータを用いて、jbuilder
でテンプレートで使用するjson
の値を生成します。views/api/somethings/new.json.jbuilderjson.roots @roots, :id, :name json.children @children, :id, :name json.grandChildren @grand_children, :id, :name # こんなJSONデータが出力されます。 # => {"roots":[{"id":1,"name":"カテゴリー1"},{"id":2,"name":"カテゴリー2"},{"id":3,"name":"カテゴリー3"}],"children":[],"grandChildren":[]}Jbulderの書き方は、いつもこちらの記事を参考にさせていただいています。
Railsのjbuilderの書き方と便利なイディオムやメソッド
"children":[]
や"grandChildren":[]
は初期値では空ですが、親カテゴリーや子カテゴリーを選択すると、root_id
やchild_id
がparams
で渡され、それによって、controller(再掲)root_id = params[:root_id] @children = root_id ? @roots.find(root_id).children : []こんな感じに、
@children
に@roots.find(root_id).children
親カテゴリーの子カテゴリーたちが持って来れます。上記の内容で、無事多階層カテゴリーが実装できました^^
その他
実際には、親カテゴリーを選択した後に子カテゴリーの
select_box
が現れる仕組みになっているので、まだまだ実装は続きますが、少し複雑なUIも実装できるようになってよかったなーと思いました。
- 投稿日:2020-08-24T22:03:41+09:00
Rails * Vue.js * Ajaxで多階層カテゴリを作った話
やったこと
Rails * Vue.js でアプリを作っています。
既存のRailsファイルに単一ファイルコンポーネントとしてVue.jsを組み込んでいくスタイルです。親カテゴリー > 子カテゴリー > 孫カテゴリー
というふうに、多階層になっているカテゴリーのUIを作りました。イメージとしては、別サイトからの引用ですが、こちらの画像のようなものです。
その時のコードを共有します。
前提条件
アプリの前提条件は次の通りです。
Rails 5.2.4.2
Vue.js 3.0.1
Rails * Vue.js * 単一ファイルコンポーネントの組み合わせで、Vue.jsを動かす方法はこちらをご覧ください。(こちらはいつかきちんとQiita記事にしたいです。。。)
Rails, Vue.js で単一ファイルコンポーネントを使用したいときの設定ファイルの書き方また、
ajax
(axios)を使った、RailsとVueのデータのやり取りの方法はこちらをご覧ください。
VueのコンポーネントにRailsのデータを使った話(SFC利用、Railsの一部にVue使用最後に、多階層カテゴリーの作成にあたっては
ancestory
というgemを利用しています。ancestory
の使い方はこちらをご覧ください。
多階層カテゴリでancestryを使ったら便利すぎた作成したコード
テンプレート
今回作成したコードはこちらです。まずはテンプレートから。
なお、わかりやすさのため、Railsにデータを送るために必要なname属性や、Railsによる自動生成のidの記載は省いています。javascript/components/select_form.vue<template> <div class="select-form"> <!-------- 親カテゴリー -------------> <div> <label>カテゴリー</label> <select @change="findChildren"> <option v-for="root in roots" :value="root.id" :key="root.id">{{ root.name }}</option> </select> </div> <!-------- 子カテゴリー -------------> <div> <select @change="findGrandChildren"> <option v-for="child in children" :value="child.id" :key="child.id">{{ child.name }}</option> </select> </div> <!-------- 孫カテゴリー -------------> <div> <select> <option v-for="gChild in grandChildren" :value="gChild.id" :key="gChild.id">{{ gChild.name }}</option> </select> </div> </div> </template> <script> // ajax通信を行うためにaxiosを読み込む import axios from 'axios'; export default { data: function() { return { roots: [], children: [], grandChildren: [], root_id: "", child_id: "" } }, mounted() { // DOM要素読み込み時に、それぞれのselectのオプションに初期値を入れる // response.data.roots などには、controllerで定義しjbuilderで生成した値が入る axios.get('/api/somethings/new.json').then(response => (this.roots = response.data.roots, this.children = response.data.children, this.grandChildren = response.data.grandChildren)) }, methods: { findChildren: function(event) { // selectボックスの値が変化したときに、optionのvalue値を取得する。 // そしてその値をroot_idに返す。findGrandChildrenも同じ let rootValue = event.target.value; return this.root_id = rootValue; }, findGrandChildren: function(event) { let childValue = event.target.value; return this.child_id = childValue; } }, watch: { // root_id, child_idの値が変化したときに、新たなリクエストを生成する root_id: function() { if (this.root_id !== "" ) { //paramsでroot_idをcontrollerに渡す。 axios.get('/api/somethings/new.json', { params: { root_id: this.root_id } }).then(response => (this.children = response.data.children)) } }, child_id: function() { if (this.child_id !== "" ) { //孫カテゴリー生成時には、root_id, child_idをcontrollerに渡す axios.get('/api/somethings/new.json', { params: { root_id: this.root_id, child_id: this.child_id } }).then(response => (this.grandChildren = response.data.grandChildren)) } } } } </script>controller
コントローラーの定義は下記の通りです。実装にあたっては、こちらの記事を参考にしました。
[Rails] Ajax通信を用いたカテゴリボックス作成なぜ
/api/
を挟んだディレクトリ構造になっているかは、先に挙げたこちらの記事をご参照ください。controllers/api/somethings_controller.rbclass Api::SomethingsController < ApplicationController def new # 親カテゴリーの生成 @roots = Category.roots root_id = params[:root_id] child_id = params[:child_id] # paramsにroot_idがあれば子カテゴリーを生成、なければ空の配列を返す。子と孫も同様。 @children = root_id ? @roots.find(root_id).children : [] @grand_children = child_id ? @children.find(child_id).children : [] render 'new', formats: 'json', handlers: 'jbuilder' end endjbuilder
上記の
controller
で定義したデータを用いて、jbuilder
でテンプレートで使用するjson
の値を生成します。views/api/somethings/new.json.jbuilderjson.roots @roots, :id, :name json.children @children, :id, :name json.grandChildren @grand_children, :id, :name # こんなJSONデータが出力されます。 # => {"roots":[{"id":1,"name":"カテゴリー1"},{"id":2,"name":"カテゴリー2"},{"id":3,"name":"カテゴリー3"}],"children":[],"grandChildren":[]}Jbulderの書き方は、いつもこちらの記事を参考にさせていただいています。
Railsのjbuilderの書き方と便利なイディオムやメソッド
"children":[]
や"grandChildren":[]
は初期値では空ですが、親カテゴリーや子カテゴリーを選択すると、root_id
やchild_id
がparams
で渡され、それによって、controller(再掲)root_id = params[:root_id] @children = root_id ? @roots.find(root_id).children : []こんな感じに、
@children
に@roots.find(root_id).children
親カテゴリーの子カテゴリーたちが持って来れます。上記の内容で、無事多階層カテゴリーが実装できました^^
その他
実際には、親カテゴリーを選択した後に子カテゴリーの
select_box
が現れる仕組みになっているので、まだまだ実装は続きますが、少し複雑なUIも実装できるようになってよかったなーと思いました。
- 投稿日:2020-08-24T14:18:57+09:00
コンポーネント内部での画面遷移 Vue.js備忘録 vol.02
はじめに
前回に引き続き、vue.jsを触ってみて詰まった所などを備忘録としてまとめてみます。
Vue.js(SPA)におけるブラウザバック問題
SPAでの実装に慣れている方からすると初歩的なミスかもしれないが…
このような画面遷移を行なっていた時/hoge
⇨/fuga
⇨/fuga?page=2
ブラウザバックを行うと/fuga?page=2
から/hoge
に戻ってしまっていた。考えられる原因
ページネーションでの遷移が
history.replaceState()
だった(現在の履歴エントリーを新しい履歴エントリーに置換する設定)
history.pushState()
にしたらいけるのでは!?!?history.pushStateにしたものの…
URLは
/fuga?page=2
⇨/fuga
へ変わるが、画面が再描画されない!!
今回作成していたページはAPIを叩いて一覧表示していたため、URLのパラメータの変更を検知して再度APIを叩く処理を書く必要がありました。対処
/fuga
⇨/fuga?page=2
や、/fuga?page=2
⇨/fuga
への遷移は同じコンポーネントを描画するため、同じコンポーネントインスタンスが再利用されます。古いインスタンスを破棄し、新しいものを生成するよりは効率的なのですが、同時にコンポーネント自体のライフサイクルが呼ばれないことも意味しています。
上記の問題を解消するためにvue-routerのナビゲーションガードを使用しました。
beforeRouteUpdate
でパラメータの変更を検知できます。index.jsbeforeRouteUpdate (to, from, next) { // ルート変更に反応する if(to.query !== from.query){ this.research(to.query.page);//APIを叩く処理 } next(); }パラメータの変更を検知し、再度APIを叩く処理を入れることで解消することができました!
最後に
今回は初歩的なミスではありましたが、もし同様のことでお困りでしたら参考になればと思います。
もしこれより良い方法などあればぜひ教えてください!
(vue-routerの公式にしっかり説明が書いてあったのでちゃんと読んでおけばよかったな〜)
- 投稿日:2020-08-24T14:13:55+09:00
【Nuxt.js】Nuxt実践編:v-model + Vuex + firebase
? この記事はWP専用です
https://wp.me/pc9NHC-yW前置き
v-modelで直接storeを書き換えて
それをfirebaseへ送信してみます?ですが実際に使う場面はほぼありません?笑
というのも、
firebaseを使って入力した値を送信さえできれば良いからです。
取得も簡単にできるので
わざわざVuexを使ってstoreに保存する必要がないのです…!なので
仕組みを見て「ふーんなるほど?」と
分かってもらえたら良いなと思います??v-model, computed, vuex, firesbase(firestore)の
解説記事を読んだ方なら、
当たり前じゃん❗️
と思うかもしれません!
それぞれの解説記事もあるので
リンクを貼っておきます??⬇️解説記事はこちら
v-model: https://wp.me/pc9NHC-kI
computed: https://wp.me/pc9NHC-wY
Vuex: https://wp.me/pc9NHC-gl https://wp.me/pc9NHC-dH
firestore: https://wp.me/pc9NHC-g4完成コード
まずは完成コードからお見せしちゃいます?
この後に、ここに至るまでの考え方などを書いていきます。
そちらを見ながらやってみてください??firestoreとの連携などは前置きリンクからご覧ください?
plugin.firebase.jsimport firebase from "firebase/app" import 'firebase/firestore' if (!firebase.apps.length) { firebase.initializeApp({ apiKey: "貼り付け", authDomain: "貼り付け", databaseURL: "貼り付け", projectId: "貼り付け", storageBucket: "貼り付け", messagingSenderId: "貼り付け", appId: "貼り付け", measurementId: "貼り付け" }) } export default firebaseindex.vue<template> <div class="page"> <input type="text" v-model="name" > <input type="text" v-model="email" > <p>{{ name }} {{ email }}</p> <button @click="submit">submit</button> </div> </template> <script> export default { computed: { name: { get() { return this.$store.getters['name'] }, set(value) { this.$store.commit('updateName', value) }, }, email: { get() { return this.$store.getters['email'] }, set(value) { this.$store.commit('updateEmail', value) }, }, }, methods: { submit () { this.$store.dispatch('submit') } }, } </script>store.index.jsimport firebase from '@/plugins/firebase' export const state = () => ({ name: "名前", email: "email", }) export const getters = { name: state => { return state.name }, email: state => { return state.email }, } export const actions = { submit ({ state }) { const db = firebase.firestore() let dbUsers = db.collection('users') dbUsers .add({ name: state.name, email: state.email, }) .then(ref => { console.log('Add ID: ', ref.id) }) }, } export const mutations = { updateName(state, newName) { state.name = newName }, updateEmail(state, newEmail) { state.email = newEmail }, }経緯
それでは解説をしていきます?
? 続きはWPでご覧ください?
https://wp.me/pc9NHC-yW
- 投稿日:2020-08-24T12:44:22+09:00
Nuxt.jsとMaterial Iconで、SPA時に文字化けする
事象は表題の通り
Material Iconの実装は以下の方法です。
【IE対応】Google Material Iconsをdata 属性を使ってスマートに表示する
上記の手法、Nuxt.jsでSSRされたときは問題なく表示されますがSPA遷移行うとユニコードがそのまま文字列として展開されてしまいます。
回避方法
これを回避する方法は2パターン考えられます。
v-htmlを使う
どうやら仮想DOMから実際にHTMLを出力される際にユニコードを文字列としてエンコードして出しているように見えます。
v-html
ディレクティブを利用することでこれを回避できます。<a href="~~~" v-html="icon"></a> ... <script> ... const icon = `<i class="material-icon" data-icon=""</i>お問い合わせ` return { icon } ... </script>ユニコードをそのまま貼り付ける
出力されるHTMLに注目してみましょう。
正常に表示できているときはHTMLに通称豆腐として展開されています。
なので、vueテンプレート側でもこの豆腐で登録することでうまく表示できるのでは?ということで試してみたところ、うまく表示されました。使っているアイコンをオブジェクトで管理することで「この豆腐なんだっけ?」も防げますね。とはいえスマートな解決とは言えないのでもっと良い解決方法が知りたいです。
- 投稿日:2020-08-24T10:34:36+09:00
Rails でherokuデプロイ時にPrecompiling assets failedが出た
環境
Rails 5.2
Ruby 2.6フロント側ではvue+vuex+vue-routerを使用
症状
herokuにプッシュしようとしたら、
Precompiling assets failed
のメッセージが出て、デプロイが失敗する。ログの上のほうには
Field 'browser' doesn't contain a valid alias configuration
のメッセージあり。原因
パッケージの依存関係に問題あり?
package.json
{ "name": "mapApp", "private": true, "dependencies": { "@rails/webpacker": "5.1.1", "axios": "^0.20.0", "google-maps-api-loader": "^1.1.1", "leader-line-vue": "^2.1.1", "vue": "^2.6.11", "vue-loader": "^15.9.2", "vue-router": "^3.4.3", "vue-template-compiler": "^2.6.11", "vuex": "^3.5.1" }, "devDependencies": { "webpack-dev-server": "^3.11.0" } }解決方法
片っ端からパッケージのアンインストールとインストールをしたら、デプロイできました。
(npmとyarnを共存させると良くないと思ったので、インストール時にはyarnを使ってます)npm uninstall vuex yarn add vuex //vuexだけでなくvue-routerや他のパッケージにも同じ操作Precompiling assets failedの原因は、scssとcss起因のものなど色々あるそうですが、
とりあえず今回はこれで解決できました
- 投稿日:2020-08-24T10:10:45+09:00
【Vue.js】v-for v-if を一緒に使うのは避けないといけない
はじめに
Vue.jsでは v-if と v-forは一緒に使うのは避けるのがドキュメントによると推奨されています
一緒に使うというのは
vuejs/hoge.vue<template v-for="item in items" v-if="item.key === 1" :key="item.id"> <li> <router-link :to="item.url">{{ item.pages }}</router-link> </li> </template>こういった タグの中にv-forとv-ifを詰め込んでいる状態のことを指しています
とある記事のコメントにてv-forとv-ifの併用を勧めてしまって、(もちろん訂正コメントも送信しています
)
今後同じことを勧めないようにアウトプットで書きました。なぜだめなのか
Vue がディレクティブを処理するとき、v-forは v-if よりも優先度が高いとされていて
上の例の中身の処理をJavaScriptで書くとJavaScript/hoge.jsthis.items.map(function (item) { if (item.key === 1) { return item.url } })このようになります
これだとresultに変更があった際に実際に表示されるitemの変更の有無にかかわらず
再度繰り返し処理されるのですじゃあどうしたいいんか
実際に表示されるitemに変化があったり条件に合うitemが増減したりする際にのみ
繰り返し処理を行うようにします。vuejs/hoge.vue<ul> <li v-for="item in resultKey"> <router-link :to="item.url">{{ item.pages }}</router-link> </li> </ul> ~~~省略 computed: { resultKey: function () { return this.items.filter(function (item) { return item.key === 1 } }) } }今回は算出プロパティを使って解決しました。
こうすることで以下の利点が得られますフィルタリングされたリストは items 配列に関連する変更があった場合に のみ 再評価されるので、フィルタリングがはるかに効率的になります。
v-for="item in activeUsers" を使用して、描画中にkeyが1のitem のみ 繰り返し処理するので、描画がはるかに効率的になります。
ロジックがプレゼンテーションレイヤから切り離され、メンテナンス(ロジックの変更/拡張)がはるかに容易になります。
(参照: Vue.js スタイルガイド v-for-と一緒に-v-if-を使うのを避ける-必須)おわり
Vue.jsって結構なんでもできちゃうのでおっこれでいけるじゃん!と思っていたものは実は推奨されていない書き方って結構あるかもですね(ドキュメントしっかり読んでいるからそんなことないわって思っている方もいるかもですが)
つまりドキュメント最強です
これ大丈夫なのかって思ったらとりあえずドキュメントですね参照記事
- 投稿日:2020-08-24T09:56:21+09:00
【Axios】Nuxt.jsで、axiosを使ってサーバーとの通信を行う。
Nuxt.js下で、サーバーとの通信を行うことができるaxiosについてまとめたいと思います。
具体的にできることとしては、サーバーからのデータを取得したり(GET)、サーバーのデータを更新したり(PATCH / PUT)することができる。
今回は、GETリクエストに絞って使い方についてみていきましょう。
モジュールを利用できるようにする
axiosではなく、@nuxtjs/axiosをインストールすることで、ファイルごとにimportする必要がなくなる。
また、レスポンス値がdataプロパティであるため、res.data
にする必要がなくなる。(後述)$ yarn add @nuxtjs/axios
nuxt.config.jsmodules: [ '@nuxtjs/axios', ],リクエスト後のレスポンスによって、処理を変える
基本形
$axios.$get(URL) .then(function (res) { //通信が成功した後の処理 }) .chach(function (error) { //通信が失敗した後の処理 }) .final(function () { //通信後に必ず実行される処理 })
- getの引数にURLを取ることで、GETリクエストを送信
- レスポンスの値は、functionの引数に格納される
実践
今回は、無料の外部サービスであるJSONPlaceholderを利用させてもらって、外部のデータを取得していきます。
Vue.js<script> export default { data () { return { posts: [] } }, mounted () { const res = $axios.$get('https://jsonplaceholder.typicode.com/posts/') .then(function (res) { this.posts = res }) } } </script>ページが読み込まれた後に、外部データを取得し、postsに格納することができる。
axiosの場合だと
res.data
にしなければならないが、@nuxtjs/axiosだとレスポンス値がdataプロパティであるため、res
だけでデータを取得できる。簡略化したリクエスト後の処理
基本形
async function () { const res = await $axios.$get(URL) //通信後の処理 }実践
Vue.js<script> export default { data () { return { posts: [] } }, mounted () { async function (context) { const res = await context.$axios.$get('https://jsonplaceholder.typicode.com/posts/') this.posts = res } } } </script>
- 投稿日:2020-08-24T00:13:28+09:00
WebWorkerで擬似チャネルインターフェイスを作りVue.jsのライフサイクルへバインドする
Vue.jsを使ったSPAで、メインスレッドは極力Vue.jsのレンダリングに使わせたいので、重い処理はWebWorkerへ投げることがあります。
WebWorkerは非同期のため
addEventListener()
などでメッセージをハンドリングするわけですが、ハンドラを普通に書くとVue.jsのライフサイクルから外れてしまいます。強引にライフサイクル内でイベントリスナを登録してしまうと、多重登録となりやすく危険です。
<div id="vue"> <input type="file" v-model="largeBlob"> <div>{{result}}</div> <button @click="heavyTask()">Parse!</button> </div>const worker = new Worker("./worker.js"); // 受けるときはライフサイクル外になってしまう worker.addEventListner("message", ({data}) => data); const vue = new Vue({ el: "#vue", data(){ return { largeBlob: undefined, result: "" }; }, methods: { async heavyTask(){ // heavyTaskが呼ばれるたびに多重登録されてしまう worker.addEventListner("message", ({data}) => this.result = data); // ライフサイクル内で送ることはできる const _largeBlob = await this.largeBlob?.arrayBuffer(); worker.postMessage(_largeBlob, [_largeBlob]); }, } });そこでWebWorkerの処理結果を
methods
内のメソッドへバインドすることで、安全にVue.jsのライフサイクルへ組み込む方法をご紹介します。擬似チャネルインターフェイス
まずDedicatedWorkersはチャネル名を指定できません。
ですが、実装していくうちコンテキストによって処理内容を変えたい場合が出てくると思います。
その答えのひとつとして "擬似チャネルインターフェイス" という方法を私は思いつきました。
なお、これはVue.jsに限った話ではなく、汎用的に使えるテクニックです。まず、送受信メッセージをオブジェクトでカプセル化して
ch
などのチャネル名を指定するプロパティを作ります。
送受信メッセージの中身は、配列としてbody
など名前を付けてプロパティへ格納すると、もし中身がArrayBuffer
だった場合はpostMessage()
の第2引数のTransferable
で指定しやすいのでオススメです。MainThreadconst message = { ch: "parse", body: [_largeBlob] }; worker.postMessage(message, message.body);いっぽう、ワーカースレッドはメインスレッドから送られてきたメッセージをチャネルごとに分けて処理する必要があります。
まずは
function _channel_parse(data)
など、チャネルインターフェイス用だと分かりやすい接頭辞とチャネル名を繋げたメソッドを作り、引数にはメインスレッドから受信したメッセージを受け取ります。そして、ワーカースレッド側のメッセージイベントハンドラを組み立てるわけですが、先ほど作ったメソッドはグローバルスコープへ定義されているはずです。
具体的にいうとDedicatedWorkerGlobalScope
かself
かglobalThis
から参照できます。ここが大事で、JavaScriptはプロパティへアクセスする際にブラケット記法を使うことで、変数値でプロパティへアクセスすることができます。
つまりself
を親オブジェクトとして、そのプロパティ(つまり_channel_
メソッド)へチャネル名でアクセスする、というのが擬似チャネルインターフェイスの特徴です。チャネルメソッドの返り値は、メインスレッド同様にオブジェクトでカプセル化しチャネル名プロパティを付与します。
WorkerThreadasync function blobParser(blob){ // 何らかの重い処理 return result; } async function _channel_parse(data){ return { ch: "parse", body: [await blobParser(data)] }; } self.addEventListner("message", ({data})=>{ const message = await self[`_channel_${data.ch}`](data); self.postMessage(message, message.body); });そして再びメインスレッドへ戻ってきます。
ここでようやくVue.jsのライフサイクルへ組み込む処理となります。
といっても、やってることは先ほどのワーカースレッドとほとんど同じです。
先ほどの親オブジェクトはDedicatedWorkerGlobalScope
でしたが、Vue.jsのライフサイクルフックはthis
で参照できるためself
をthis
に変えるだけです。MainThreadnew Vue({ el: "#vue", data(){ return { largeblob: undefined, result: "" }; }, mounted(){ // チャネル名でmethodsへバインドする // マウント後に1回だけ登録されるので、多重登録は発生しない worker.addEventListener("message", ({data}) => this[`_channel_${data.ch}`](data)); }, methods: { // WebWorkerの処理結果メッセージを引数として受け取れる _channel_parse(data){ this.result = data.body[0]; }, async heavyTask(){ const message = { ch: "parse", body: [await this.largeBlob?.arrayBuffer()] }; worker.postMessage(message, message.body); }, } });