20200824のvue.jsに関する記事は11件です。

【Vuetify】メインコンテンツの背景色を変更する

smart-sr.png

赤枠部分の背景色を変更するのに地味にハマったのでメモ。

環境

Vuetify 2.3.9

変更方法

テーマをカスタマイズしてbackgroundを設定する。

vuetify.ts
import 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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】Vue.js devtoolsが表示出来ない時にみる

vue.js備忘録、第二弾。
誰かの参考になれば幸いです。

Vue.js devtoolsとは

スクリーンショット 2020-08-24 22.38.12.png

Vueのデバッグを行う際に非常に便利な拡張機能です。
コンポーネント構造を直感的操作で確認出来て、プロパティーの中身も見る事が出来ます。

導入方法は非常に簡単でこちらからダウンロードしてくるだけです。

正しい動き

検証でデベロッパーツールを開き、Vueタブが表示されていればOK
スクリーンショット 2020-08-24 22.18.38.png

Vueタブが表示されないパターン①

「ファイルの URL へのアクセスを許可する」がオフになっている場合はオンにしてみましょ。
スクリーンショット 2020-08-24 22.26.44.png

Vueタブが表示されないパターン②

当たり前ですが、productionモードでは表示されないです。
開発モードで検証しましょう。
スクリーンショット 2020-08-24 22.31.41.png

Vueタブが表示されないパターン③

ここまで見てもVueタブが出てきてくれなかったら、キャッシュを疑いましょう。
NetworkタブのDisable cacheにチェックを入れて再読み込みして改善するか確認しましょう。

スクリーンショット 2020-08-24 22.35.32.png

これで大体解決出来るはずですm(__)m

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails * Vue.jsで多階層カテゴリを作った話

やったこと

Rails * Vue.js でアプリを作っています。
既存のRailsファイルに単一ファイルコンポーネントとしてVue.jsを組み込んでいくスタイルです。

親カテゴリー > 子カテゴリー > 孫カテゴリー

というふうに、多階層になっているカテゴリーのUIを作りました。イメージとしては、別サイトからの引用ですが、こちらの画像のようなものです。
category.gif

その時のコードを共有します。

前提条件

アプリの前提条件は次の通りです。

  • 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.rb
class 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
end

jbuilder

上記のcontrollerで定義したデータを用いて、jbuilderでテンプレートで使用するjsonの値を生成します。

views/api/somethings/new.json.jbuilder
json.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_idchild_idparamsで渡され、それによって、

controller(再掲)
  root_id = params[:root_id]
  @children = root_id ? @roots.find(root_id).children : []

こんな感じに、@children@roots.find(root_id).children親カテゴリーの子カテゴリーたちが持って来れます。

上記の内容で、無事多階層カテゴリーが実装できました^^

その他

実際には、親カテゴリーを選択した後に子カテゴリーのselect_boxが現れる仕組みになっているので、まだまだ実装は続きますが、少し複雑なUIも実装できるようになってよかったなーと思いました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails * Vue.js * Ajaxで多階層カテゴリを作った話

やったこと

Rails * Vue.js でアプリを作っています。
既存のRailsファイルに単一ファイルコンポーネントとしてVue.jsを組み込んでいくスタイルです。

親カテゴリー > 子カテゴリー > 孫カテゴリー

というふうに、多階層になっているカテゴリーのUIを作りました。イメージとしては、別サイトからの引用ですが、こちらの画像のようなものです。
category.gif

その時のコードを共有します。

前提条件

アプリの前提条件は次の通りです。

  • 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.rb
class 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
end

jbuilder

上記のcontrollerで定義したデータを用いて、jbuilderでテンプレートで使用するjsonの値を生成します。

views/api/somethings/new.json.jbuilder
json.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_idchild_idparamsで渡され、それによって、

controller(再掲)
  root_id = params[:root_id]
  @children = root_id ? @roots.find(root_id).children : []

こんな感じに、@children@roots.find(root_id).children親カテゴリーの子カテゴリーたちが持って来れます。

上記の内容で、無事多階層カテゴリーが実装できました^^

その他

実際には、親カテゴリーを選択した後に子カテゴリーのselect_boxが現れる仕組みになっているので、まだまだ実装は続きますが、少し複雑なUIも実装できるようになってよかったなーと思いました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンポーネント内部での画面遷移 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.js
 beforeRouteUpdate (to, from, next) {
    // ルート変更に反応する
    if(to.query !== from.query){
      this.research(to.query.page);//APIを叩く処理
    }
    next();
  }

パラメータの変更を検知し、再度APIを叩く処理を入れることで解消することができました!

最後に

今回は初歩的なミスではありましたが、もし同様のことでお困りでしたら参考になればと思います。
もしこれより良い方法などあればぜひ教えてください!
(vue-routerの公式にしっかり説明が書いてあったのでちゃんと読んでおけばよかったな〜)

Vue-Router 動的ルートマッチング

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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.js
import firebase from "firebase/app"
import 'firebase/firestore'

if (!firebase.apps.length) {
 firebase.initializeApp({
  apiKey: "貼り付け",
  authDomain: "貼り付け",
  databaseURL: "貼り付け",
  projectId: "貼り付け",
  storageBucket: "貼り付け",
  messagingSenderId: "貼り付け",
  appId: "貼り付け",
  measurementId: "貼り付け"
 })
}

export default firebase
index.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.js
import 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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt.jsとMaterial Iconで、SPA時に文字化けする

事象は表題の通り

Material Iconの実装は以下の方法です。

【IE対応】Google Material Iconsをdata 属性を使ってスマートに表示する

上記の手法、Nuxt.jsでSSRされたときは問題なく表示されますがSPA遷移行うとユニコードがそのまま文字列として展開されてしまいます。

SSR
スクリーンショット 2020-08-24 12.25.12.png

SPA
スクリーンショット 2020-08-24 12.25.04.png

回避方法

これを回避する方法は2パターン考えられます。

v-htmlを使う

どうやら仮想DOMから実際にHTMLを出力される際にユニコードを文字列としてエンコードして出しているように見えます。
v-htmlディレクティブを利用することでこれを回避できます。

<a href="~~~" v-html="icon"></a>

...
<script>
  ...
  const icon = `<i class="material-icon" data-icon="&#57534;"</i>お問い合わせ`
  return { icon }
  ...
</script>

ユニコードをそのまま貼り付ける

出力されるHTMLに注目してみましょう。

SSR
スクリーンショット 2020-08-24 12.31.34.png

SPA
スクリーンショット 2020-08-24 12.32.19.png

正常に表示できているときはHTMLに通称豆腐として展開されています。
なので、vueテンプレート側でもこの豆腐で登録することでうまく表示できるのでは?ということで試してみたところ、うまく表示されました。

スクリーンショット 2020-08-24 12.35.46.png

使っているアイコンをオブジェクトで管理することで「この豆腐なんだっけ?」も防げますね。とはいえスマートな解決とは言えないのでもっと良い解決方法が知りたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
のメッセージあり。

原因

パッケージの依存関係に問題あり?

https://stackoverflow.com/questions/61956924/rails-field-browser-doesnt-contain-a-valid-alias-configuration-when-pushing

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起因のものなど色々あるそうですが、
とりあえず今回はこれで解決できました

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】v-for v-if を一緒に使うのは避けないといけない

はじめに

Vue.jsでは v-if と v-forは一緒に使うのは避けるのがドキュメントによると推奨されています:ok_hand_tone2:

一緒に使うというのは

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を詰め込んでいる状態のことを指しています:point_up_2_tone2:

とある記事のコメントにてv-forとv-ifの併用を勧めてしまって、(もちろん訂正コメントも送信しています:ok_hand:
今後同じことを勧めないようにアウトプットで書きました。

なぜだめなのか

Vue がディレクティブを処理するとき、v-forは v-if よりも優先度が高いとされていて
上の例の中身の処理をJavaScriptで書くと

JavaScript/hoge.js
this.items.map(function (item) {
  if (item.key === 1) {
    return item.url
  }
})

このようになります
これだとresultに変更があった際に実際に表示されるitemの変更の有無にかかわらず
再度繰り返し処理されるのです:imp:

じゃあどうしたいいんか

実際に表示されるitemに変化があったり条件に合うitemが増減したりする際にのみ
繰り返し処理を行うようにします。:pig_nose:

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
      }
    })
  }
}

今回は算出プロパティを使って解決しました。
こうすることで以下の利点が得られます:moneybag:

フィルタリングされたリストは items 配列に関連する変更があった場合に のみ 再評価されるので、フィルタリングがはるかに効率的になります。
v-for="item in activeUsers" を使用して、描画中にkeyが1のitem のみ 繰り返し処理するので、描画がはるかに効率的になります。
ロジックがプレゼンテーションレイヤから切り離され、メンテナンス(ロジックの変更/拡張)がはるかに容易になります。
(参照: Vue.js スタイルガイド v-for-と一緒に-v-if-を使うのを避ける-必須

おわり

Vue.jsって結構なんでもできちゃうのでおっこれでいけるじゃん!と思っていたものは実は推奨されていない書き方って結構あるかもですね(ドキュメントしっかり読んでいるからそんなことないわって思っている方もいるかもですが)
つまりドキュメント最強です
これ大丈夫なのかって思ったらとりあえずドキュメントですね:writing_hand_tone4:

参照記事

Vue.jsドキュメント スタイルガイド

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Axios】Nuxt.jsで、axiosを使ってサーバーとの通信を行う。

Nuxt.js下で、サーバーとの通信を行うことができるaxiosについてまとめたいと思います。

具体的にできることとしては、サーバーからのデータを取得したり(GET)、サーバーのデータを更新したり(PATCH / PUT)することができる。

今回は、GETリクエストに絞って使い方についてみていきましょう。

モジュールを利用できるようにする

axiosではなく、@nuxtjs/axiosをインストールすることで、ファイルごとにimportする必要がなくなる。
また、レスポンス値がdataプロパティであるため、res.dataにする必要がなくなる。(後述)

$ yarn add @nuxtjs/axios
nuxt.config.js
  modules: [
    '@nuxtjs/axios',  
  ],

リクエスト後のレスポンスによって、処理を変える

基本形

$axios.$get(URL)
  .then(function (res) {
    //通信が成功した後の処理
  })
  .chach(function (error) {
    //通信が失敗した後の処理
  })
  .final(function () {
    //通信後に必ず実行される処理
  })
  1. getの引数にURLを取ることで、GETリクエストを送信
  2. レスポンスの値は、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>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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で指定しやすいのでオススメです。

MainThread
const message = {
    ch: "parse",
    body: [_largeBlob]
};
worker.postMessage(message, message.body);

いっぽう、ワーカースレッドはメインスレッドから送られてきたメッセージをチャネルごとに分けて処理する必要があります。

まずはfunction _channel_parse(data)など、チャネルインターフェイス用だと分かりやすい接頭辞とチャネル名を繋げたメソッドを作り、引数にはメインスレッドから受信したメッセージを受け取ります。

そして、ワーカースレッド側のメッセージイベントハンドラを組み立てるわけですが、先ほど作ったメソッドはグローバルスコープへ定義されているはずです。
具体的にいうとDedicatedWorkerGlobalScopeselfglobalThisから参照できます。

ここが大事で、JavaScriptはプロパティへアクセスする際にブラケット記法を使うことで、変数値でプロパティへアクセスすることができます。
つまりselfを親オブジェクトとして、そのプロパティ(つまり_channel_メソッド)へチャネル名でアクセスする、というのが擬似チャネルインターフェイスの特徴です。

チャネルメソッドの返り値は、メインスレッド同様にオブジェクトでカプセル化しチャネル名プロパティを付与します。

WorkerThread
async 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で参照できるためselfthisに変えるだけです。

MainThread
new 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);
        },
    }
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む