20201112のvue.jsに関する記事は12件です。

Vue.jsのフィルタを使って消費税と3桁ごとのカンマをつける

※本記事は自分のブログ( https://haru0101.github.io/velosa/ )、およびZenn.devにも同じ内容を投稿しております。

フィルタとは

dataなどを整形するときに用います。

今回では、『1000』というdataを渡したら、消費税込みの『1100』、3桁ごとのカンマのついた『1,100』を表示させるのがゴールです。

computedとの違いは?

フィルターはcomputedと違い、連結することができます。

先に述べたように、消費税の税率をかけたあとに、その値を3桁ごとにカンマで区切ることができます。

また、もとのdataを変更せずにあくまで表示するときだけのフィルターです。

実際のコード

『|』でフィルタを連結することができます。

引数はフィルタ側では受け取れますが、呼び出すときは第1引数のみ省略することができます。

<div id="app">
    <h2>消費税込み価格</h2>
    <p>{{ price | includedTax }}</p>
    <!-- 結果:1100 -->

    <h2>消費税込み + 3桁ごとのカンマ</h2>
    <p>{{ price | includedTax | formatNumber }}</p>
    <!-- 結果:1,100 -->

</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            price: 1000
        },
        filters: {
            includedTax: function (price) {
                return price * 1.1
            },
            formatNumber: function (price) {
                return price.toLocaleString()
            }
        }
    })
</script>

第2引数以上ある場合

第1引数はかってにフィルタをかけるdata、今回でいうpriceだと判断してくれます。

よって第2引数以上になった場合から、フィルタの呼び出しの際に引数を指定します。

<div id="app">
    <h2>消費税込み価格</h2>
    <!-- 第1引数のpriceは省略できる -->
    <!-- 第2引数のtaxRateから指定する -->
    <p>{{ price | includedTax(1.1) }}</p>
    <!-- 結果:1100 -->
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            price: 1000
        },
        filters: {
            includedTax: function (price, taxRate) {
                return price * taxRate
            }
        }
    })
</script>

グローバルフィルタについて

今まで定義したのはローカルフィルタであり、コンポーネント内でしか参照することができません。

どのコンポーネントからも参照できるようにするには、グローバルフィルタを定義する必要があります。

呼び出し方は一緒ですが、定義の仕方が違います。

ポイントは、Vueの読み込みのあと、かつ、インスタンス化の前です。

書き方

<div id="app">
    <h2>消費税込み価格</h2>
    <!-- 第1引数のpriceは省略できる -->
    <!-- 第2引数のtaxRateから指定する -->
    <p>{{ price | includedTax(1.1) }}</p>
    <!-- 結果:1100 -->
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    Vue.filter('includedTax', function(price){
        return price * 1.1
    })
    var app = new Vue({
        el: '#app',
        data: {
            price: 1000
        }
    })
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(6)アプリケーションメニューとRouterでの画面遷移

Vuetifyのアプリケーションメニュー、上部のバーを追加して、アプリケーションの骨格を作ってみます。さらに、VueのRouter機能を導入することで、シングルページアプリケーション(SPA)の画面遷移を実装してみます。

説明の途中でgitが必要となるので、gitのインストールがまだの場合は、gitをインストールしておいてください。

参考文献

プロジェクトの作成

まず、Routerの機能を追加したプロジェクトを作成します。

> vue create appbar01

vue_project_1.png

Manually select featuresを選択します。

さらに、Routerを選択します。他の項目はデフォルトのままとします。

vue_project_2.png

Vueのバージョンは、2.xを選択し、残りの選択項目も全てデフォルトとします。
次に、Vuetifyをプロジェクトに追加します。

> cd appbar01
> vue add vuetify

Vuetifyのインストールは、デフォルトのままです。
Visual Studio Codeを起動して、Terminalから、

> npm run serve

と入力し、Vueのコンパイルをして、開発用のWebサーバを起動します。ブラウザを起動してVuetifyの画面が表示されることも確認します。(詳細は、こちらを参考にしてください)

vuetify_1.png

プロジェクトを作成するときに選択したRouterの実装が、Vuetifyをインストールしたことで上書きされてしまったので、App.vueを元に戻したいと思います。ソースコードがgitで管理されているので、Visual Studio Codeから簡単に元に戻すことができます。

vc_1.png

上記のように、Visual Studio Codeのソースコード管理メニューを選択し、赤丸のアイコン(Discard changes)をクリックすると、Vuetifyでインストールされた部分が元に戻ります。

vuetify_2.png

src/components/HelloWorld.vue はVuetifyの変更が入ったままなので、VueとVuetifyのアイコンが同時に表示されていますが、とりあえず、"Home" と "About"のリンクが上部に表示されたと思います。それぞれは、/ /aboutへリンクされていますが、これらは、VueのRouterの機能が使われています。

Router

Routerがどのように動いているか見てみます。
Visual Studio Codeから、src/router/index.js を見てください。

...

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

ここで、"/"はHome('../views/Home.vue')を呼び出し、"/about"はAbout('../views/About.vue')を呼び出すように設定されています。
aboutページについては、Routerをインストールした時に入っているコメントにもあるように、/aboutが呼び出された時に初めて画面が読み込まれる遅延ローディング(lazy loading)の仕組みを使うようになっています。こうすることで、"/"が呼ばれた時にはAbout画面が読み込まれないので、ロードが早くなるメリットがあります。(詳細は、遅延ローディングルートを参照)

さらに、Home.vue は、コンポーネントとして、src/components/HelloWorld.vue を読み込んでいます。VueとVuetifyのアイコンが表示されているのは、HelloWorld.vueがそのようになっているためです。

App.vue, v-app-bar

ここで、上部に検索などで使うためのバーと、左側にメニューを追加してみたいと思います。
これらは、VuetifyのUIコンポーネントに用意されている、<v-app-bar><v-navigation-drawer> を使うことで、簡単に追加することができます。

App.vueを開いて、<v-app> <v-main>で囲みます。

<template>
  <v-app>
    <v-main>
      <div id="nav">
        <router-link to="/">Home</router-link> |
        <router-link to="/about">About</router-link>
      </div>
      <router-view />
    </v-main>
  </v-app>
</template>

vc_2.png

<v-app> の下に、<v-app-bar>を追加します。
<v-app> <v-main> <v-container> <v-app-bar> <v-navigation-drawer> をどのように使えば良いかは、VuetifyのApplicationガイドに詳しく説明されています。

<template>
  <v-app>
    <v-app-bar app color="primary" dark>
      <v-app-bar-nav-icon></v-app-bar-nav-icon>

      <v-spacer></v-spacer>
      <v-btn icon>
        <v-icon>mdi-magnify</v-icon>
      </v-btn>
      <v-btn icon>
        <v-icon>mdi-filter</v-icon>
      </v-btn>
      <v-btn icon>
        <v-icon>mdi-dots-vertical</v-icon>
      </v-btn>
    </v-app-bar>

    <v-main>
      <div id="nav">
        <router-link to="/">Home</router-link> |
        <router-link to="/about">About</router-link>
      </div>
      <router-view />
    </v-main>
  </v-app>
</template>

これで、上部にメニュー、検索、フィルターなどのアイコンが表示されたバーが追加されました。
<v-app-bar> に指定しているapp というプロパティは、<v-main>の外にあるVuetifyのUIコンポーネントを、自動的に、正しくレイアウトするために必要となります。

vuetify_3.png

v-navigation-drawer

次に、メニューを追加してみようと思います。
メニューは、<v-navigation-drawer> で追加できます。メニューは左上のアイコンをクリックすると左側から表示され、メニューを選択すると、自動的にメニュー自体はクローズするようにします。
また、メニューには、ログインした時のアバター(写真)の表示と、Home, About画面への遷移を付け加えたいと思います。

まずは、メニューを左に表示してみます。

<template>
  <v-app>
    <v-navigation-drawer permanent app>
      <v-list nav dense>
        <v-list-item-group active-class="deep-purple--text text--accent-4">
          <v-list-item>
            <v-list-item-title>Home</v-list-item-title>
          </v-list-item>

          <v-list-item>
            <v-list-item-title>About</v-list-item-title>
          </v-list-item>

        </v-list-item-group>
      </v-list>
    </v-navigation-drawer>

    <v-app-bar app color="primary" dark>
      <v-app-bar-nav-icon></v-app-bar-nav-icon>

      <v-spacer></v-spacer>
      <v-btn icon>
        <v-icon>mdi-magnify</v-icon>
      </v-btn>
      <v-btn icon>
        <v-icon>mdi-filter</v-icon>
      </v-btn>
      <v-btn icon>
        <v-icon>mdi-dots-vertical</v-icon>
      </v-btn>
    </v-app-bar>

    <v-main>
      <div id="nav">
        <router-link to="/">Home</router-link> |
        <router-link to="/about">About</router-link>
      </div>
      <router-view />
    </v-main>
  </v-app>
</template>

ここで、下半分の<v-app-bar><v-main>の部分は、上記と同じです。
<v-navigation-drawer> のpermanentは、常にメニューを表示するためのプロパティです。<v-app-bar> と同様に、appプロパティを指定してレイアウトが正しく行われるようにしています。

vuetify_4.png

メニューアイコンを使って表示

では、メニューアイコンをクリックするとメニューが表示され、再度クリックするとメニューが非表示になるようにしてみます。さらに、メニューをクリックすると、//about にそれぞれリンクが遷移するようにしてみます。
App.vueを以下のように変更します。

<template>
  <v-app>
    <v-navigation-drawer v-model="drawer" app>
      <v-list nav dense>
        <v-list-item-group active-class="deep-purple--text text--accent-4">
          <v-list-item v-on:click="hideDrawer" to="/">
            <v-list-item-title>Home</v-list-item-title>
          </v-list-item>
          <v-list-item v-on:click="hideDrawer" to="/about">
            <v-list-item-title>About</v-list-item-title>
          </v-list-item>
        </v-list-item-group>
      </v-list>
    </v-navigation-drawer>

    <v-app-bar app color="primary" dark>
      <v-app-bar-nav-icon v-on:click="drawer = true"></v-app-bar-nav-icon>
      <v-spacer></v-spacer>
      <v-btn icon>
        <v-icon>mdi-magnify</v-icon>
      </v-btn>
      <v-btn icon>
        <v-icon>mdi-filter</v-icon>
      </v-btn>
      <v-btn icon>
        <v-icon>mdi-dots-vertical</v-icon>
      </v-btn>
    </v-app-bar>

    <v-main>
      <div id="nav">
        <router-link to="/">Home</router-link> |
        <router-link to="/about">About</router-link>
      </div>
      <router-view />
    </v-main>
  </v-app>
</template>

<script>
export default {
  data: function () {
    return {
      drawer: false
    };
  },
  methods: {
    hideDrawer: function () {
      this.drawer = false;
    },
  }
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  padding: 30px;
}

#nav a {
  font-weight: bold;
  color: #2c3e50;
}

#nav a.router-link-exact-active {
  color: #42b983;
}
</style>

vc_5.png

変更したところは以下のようなところです。

<v-navigation-drawer> に変数drawerを追加して、メニューの表示・非表示をコントロールできるようにしています。<v-navigation-drawer> では、v-modelでバインドされた変数(名前は任意です)がfalse、あるいは、変数そのものが存在しない時は、メニューが表示されず、バインドされた変数がtrueの時には表示されるようになっています。
したがって、クリックするたびに、trueとfalseを切り替えてあげることで、表示・非表示を切り替えることができるわけです。

まず、<v-app-bar-nav-icon> にクリックした時の動作を追加しています。

 <v-app-bar-nav-icon
    v-on:click="drawer = true"
></v-app-bar-nav-icon>

<v-list-item> には、クリック時に、hideDrawer()が呼び出されるようにして、<script> の部分に、

  methods: {
    hideDrawer: function () {
      this.drawer = false;
    },
  }

として、変数drawerをfalseに設定するようにしています。これによって、メニューがクリックされると、メニューが閉じるようになっています。

また、メニューのHomeやAboutがクリックされたら、"/" "/about" にページ内で遷移するように、<v-list-item> には、to= を使ってそれぞれのURLにリンクしています。もし、Routerで設定している自分自身のアプリケーション以外の外部のサイトへのリンクをするには、toの代わりに、href="https://www.google.comのように指定します。

まとめ

VueのRouter機能を使って、複数ページに遷移するシングルページアプリケーション(SPA)を作成しました。app-bar, navigation-drawerを使ったアプリケーションのフレームも作成してみました。

おまけ: Visual Studio Codeのショートカット

覚えてくと便利な、Visual Studio Codeのショートカットを書いておきます。
詳細の一覧は、Windowsショートカットと、Mac用ショートカットにありますが、私がよく使うものを2つだけ。

  • (Mac) Command + Shift + y, (Windows) Control + Shift + y デバッグコンソールの表示・非表示を切り替えできます。
  • (Mac) Command + Shift + p, (Windows) Control + Shift + p   上部のコマンドパレットを開きます。Format Documentと入力するとエディターのコードをフォーマットできます。ショートカットをカスタマイズして割当てすればいいのですが。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(5)Vuetifyでレスポンシブデザイン

VuetifyはPCとスマホの両方をサポートできるように、グリッドシステムを採用しています。今回は、Vuetifyでのグリッドの設定方法をみてみようと思います。

参考ドキュメント

グリッドを使ってレイアウトする

Vuetifyのグリッドは、Bootstrapなどと同様に、<v-container>...</v-container>の中に、<v-row>...</v-row><v-col>...</v-col>を使って、横が12のcolumnグリッドの中に、要素を置いていく方式を取っています。

vcontainer1.001.png

実際に、プログラムで確認してみましょう。まず、新しいプロジェクトを作ります。

> vue create grid01
> cd grid01
> vue add vuetify

全てDefaultを選択してください。

また、前回と同様、"src/components/HelloWorld.vue"を変更していきます。
グラフィックの表示を確認するためなので、上半分くらいだけ残してあとは、削除します。

<template>
  <v-container>
    <v-row class="text-center">
      <v-col cols="12">
        <v-img
          :src="require('../assets/logo.svg')"
          class="my-3"
          contain
          height="200"
        />
      </v-col>

      <v-col class="mb-4">
        <h1 class="display-2 font-weight-bold mb-3">
          Welcome to Vuetify
        </h1>

        <p class="subheading font-weight-regular">
          For help and collaboration with other Vuetify developers,
          <br>please join our online
          <a
            href="https://community.vuetifyjs.com"
            target="_blank"
          >Discord Community</a>
        </p>
      </v-col>
    </v-row>
  </v-container>
</template>

vscode1.png

Terminalから、>npm run serveを実行してから、デバッガからブラウザを実行すると、以下のような画面がブラウザに表示されます。

grid1.png

ここで、ロゴと"Welcome to Vuetify"のテキストを横に並べてみたいと思います。
4行目のlogo.svgを要素として持っているカラム<v-col cols="12">を、<v-col cols="4"> と変更すると、横に並んで表示されると思います。colsは、カラムの幅の指示をするための属性で、横幅は合計12になるように、なります。つまり、"Welcom to Vuetify"の文字列は、cols="8"となっています。

ここで、少しわかりやすくするために、それぞれのv-row, v-colの周りに線が表示されるようにします。
"HelloWorld.vue"の下に、以下を追加します。

<style>
.border {
  border: solid 1px;
}
</style>

さらに、2箇所の<v-col>のclassにborderを追加してあげます。追加するところだけを以下に書いています。

<template>
  <v-container>
    <v-row class="text-center">
      <v-col cols="4" class="border">
        <v-img
          ...
        />
      </v-col>

      <v-col class="mb-4 border">
        <h1 class="display-2 font-weight-bold mb-3">
          Welcome to Vuetify
        </h1>
          ...
      </v-col>
    </v-row>
  </v-container>
</template>

vscode2.png

ブラウザでの表示は、以下のようになります。cols="4"とcols="8"で、1/3と2/3の割合で表示されていることがわかります。

grid3.png

v-spacer

デザイン上、スペースを入れたいことがありますが、Vuetifyには、スペースをとるための<v-spacer>というタグがあります。ロゴと"Welcome..."の文字列の間に、さらにスペースを入れてみます。

<template>
  <v-container>
    <v-row class="text-center">
      <v-col cols="4" class="border">
        <v-img
          ...
        />
      </v-col>

      <v-spacer cols="1" />

      <v-col cols="7" class="mb-4 border">
        <h1 class="display-2 font-weight-bold mb-3">
          Welcome to Vuetify
        </h1>
          ...
      </v-col>
    </v-row>
  </v-container>
</template>

このとき、"Welcome..."の文字列にも、横幅の合計が12になるように、cols="7"を設定する必要があります。

grid5.png

レスポンシブデザイン

横幅を狭くした時に、見やすくなるように、ロゴと文字列が縦に並ぶようにしてみましょう。

グリッドシステムでは、以下のようにユーザが使用している端末の横幅に合わせて、デザインを変更するようにできています。

vcontainer.001.png

プログラムでは、横幅がどのくらい(何ピクセル)の時に、それぞれのレイアウトを切り替えるかを設定する必要があります。Grid Systemのドキュメントを見ると、Vuetifyでは、xs(<600px)から、xl(>1904px)まで5段階に分けてレイアウトを決められることがわかります。

例えば、xs(<600px)の時に、上記の一番右のレイアウト、つまり各要素が横幅100%となり、縦に一列に並ぶようにして、600pxを超えたら、横に並ぶようにするには、

  1. xs(<600px)では、ロゴの横幅を100%とするために、ロゴに、cols="12"を指定する
  2. sm(>600px)では、ロゴの横幅を33%とするために、ロゴに、cols="4"を指定する

とします。2.を指定するには、sm=を使って以下のように書きます。

<v-col cols="12" sm="4">

smが指定されれば、それより大きな画面では、smが使用されます。
実際に.vueファイルを修正して試してみましょう。

<template>
  <v-container>
    <v-row class="text-center">
      <v-col cols="12" class="border" sm="4">
...
      </v-col>

      <v-spacer cols="1"/>

      <v-col cols="7" class="mb-4 border">
...      </v-col>
    </v-row>
  </v-container>
</template>

grid6.png

ブラウザの横幅を小さくしていくと、意図したように動作することが確認できると思います。また、ここでは、回り込んだためにスペースは削除され、"Welcome..."の文字列の幅は、cols="7" で指定されているので、ロゴより狭くなっています。

まとめ

Vuetifyのグリッドシステムを使って、レスポンシブデザインを実現する方法を見てみました。

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

(4)Vue によるリアクティブプログラミング その2

「(3)Vueによるリアクティブプログラミング その1」の続きです。v-formethodで実際のアプリケーションで使う処理を追加してみます。
例として、入力フィールドに入力された文字列を画面上にリスト表示するようなプログラムを作ってみます。

参考ドキュメント

"v-for"でループ

配列をループで回してリスト表示するには、v-forというキーワードを使います。
Vueのドキュメント(リストレンダリング)を参考にして、プロジェクトは、「(3)VueによるReactiveプログラミング その1」で使った"react01"を変更します。
最初に、配列に固定の文字列を入れて、その配列をv-forを使って表示してみます。

"HelloWorld.vue"のファイルを以下のように変更します。
<v-list>...</v-list>の部分と、<script>...</script>の、dataを変更しています。

<template>
  <v-container>
    <v-list>
      <v-list-item v-for="item in messageList" :key="item.message" link>
        <v-list-item-title>{{ item.message }}</v-list-item-title>
      </v-list-item>
    </v-list>
    <v-text-field 
      label="Input your message here."
      prepend-icon="mdi-message-text-outline"
      v-model="inputMessage" />
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: function () {
    return {
      messageList: [
        {message: 'message 1'},
        {message: 'message 2'},
        {message: 'message 3'}
      ],

    }
  }
};
</script>

Visual Studio Codeの画面で見るとこんな感じです。

v-list-vscode2.png

Visual Studio CodeのTerminal で、> npm run serveを実行して、ローカルにサーバを起動したら、デバッグのアイコンをクリックして、Launch Chrome...の緑色の三角アイコンをクリックします。そうすると、以下のような画面が表示されるはずです。

v-list.png

ここでは、まず、todoListという配列に、messageのキーを持った連想配列を3個、初期値として格納しています。それを、<v-list-item>v-for="item in todoList"itemという変数に順番に読み出して、{{item.message}}で画面に表示しています。
ここで、:keyは必ずしも必要ではないですが、Vueのプログラムでは付加することを推奨されています。通常はDB上のキー項目(例えばドキュメントIDのようなユニークなキー)を使うと思いますが、ここでは簡略に、messageそのものをキーとして指定しています。

:keyv-bind:key のVueで用意されている省略記法です。詳しくは、Vueのマニュアル、v-bindの省略記法を参照してください。

ボタンを押したときの処理を追加

次に、入力フィールドに文字列を入れて、ボタンを押すと今のmessageが追加されるようにしてみます。
ボタンを押したときの処理を行う関数を、methods:という名前で、登録します。
先ほどと同様、HelloWorld.vueを以下のように修正します。

<template>
  <v-container>
    <v-list>
      <v-list-item v-for="item in messageList" :key="item.message" link>
        <v-list-item-title>{{ item.message }}</v-list-item-title>
      </v-list-item>
    </v-list>
    <v-text-field 
      label="Input your message here."
      prepend-icon="mdi-message-text-outline"
      v-model="inputMessage" />
    <v-btn v-on:click="addMessage">Add</v-btn>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: function () {
    return {
      messageList: [],
      inputMessage: ''
    }
  },
  methods: {
    addMessage: function() {
      this.messageList.push({message: this.inputMessage});
    }
  }
};
</script>

これでデバッガからブラウザを起動すると以下のように表示され、入力フィールドに文字列を入れて、Addボタンを押すと、上部に入力したメッセージが表示されます。

v-list-action.png

ここでの動作は以下のようになっています。

  1. 入力フィールドに入力された文字列は、v-modelによって、変数inputMessageに格納(双方向バインド)されます。
  2. <v-btn>がクリックされると、v-on:clickで指定された関数addMessageが呼び出されます。
  3. messageListに入力テキストを、messageのキーが付いた連想配列{message: this.inputMessage}を追加します。ここで、dataで宣言した変数を参照するには、thisをつける必要があることに注意してください。
  4. あとは、最初に確認した通り、<v-list-item>を、v-forで繰り返してmessageListから要素を順番に取り出して、表示しています。

ここで、3.の配列の関数pushが、Vueが変化したことを検知できる関数なので、messageListが変化したことがVueに伝わり、表示も自動的に変更されることになります。Vueが配列の変化を検知できる関数はドキュメント「配列の変化を検出」にあるものに限られています。

まとめ

この記事では、Vueの機能を使って、変数のバインド、ループの処理、ボタンを押したときの処理の追加方法を見てみました。

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

[解決]Rails6にVuetifyを導入した時のwebpackerエラー "ActionView::Template::Error Webpacker can't find hello_vue ~"

エラーになった過程

vueをrails6に導入して、vuetifyを追加。→試しにvuetifyのパーツをコピペ。→うまく機能しているかブラウザで確かめるため、rails s→コンパイル時にエラー発生。

エラー内容

ActionView::Template::Error (Webpacker can't find hello_vue in /usr/src/myapp/public/packs/manifest.json. Possible causes:
1. You want to set webpacker.yml value of compile to true for your environment
   unless you are using the `webpack -w` or the webpack-dev-server.
2. webpack has not yet re-run to reflect updates.
3. You have misconfigured Webpacker's config/webpacker.yml file.
4. Your webpack configuration is not creating a manifest.
Your manifest contains:
{
  "application.js": "/packs/js/application-9afcbb5693aa87623e69.js",
  "application.js.map": "/packs/js/application-9afcbb5693aa87623e69.js.map",
  "components/user.js": "/packs/js/components/user-156f569cb5c8b04ee2e1.js",
  "components/user.js.map": "/packs/js/components/user-156f569cb5c8b04ee2e1.js.map",
  "entrypoints": {
    "application": {
      "js": [
        "/packs/js/application-9afcbb5693aa87623e69.js"
      ],
      "js.map": [
        "/packs/js/application-9afcbb5693aa87623e69.js.map"
      ]
    },
    "components/user": {
      "js": [
        "/packs/js/components/user-156f569cb5c8b04ee2e1.js"
      ],
      "js.map": [
        "/packs/js/components/user-156f569cb5c8b04ee2e1.js.map"
      ]
    },
    "main": {
      "js": [
        "/packs/js/main-f6918c73db91592ebf7f.js"
      ],
      "js.map": [
        "/packs/js/main-f6918c73db91592ebf7f.js.map"
      ]
    }
  },
  "main.js": "/packs/js/main-f6918c73db91592ebf7f.js",
  "main.js.map": "/packs/js/main-f6918c73db91592ebf7f.js.map"
}
):
    11: 
    12:   <body>
    13:     <%= yield %>
    14:     <%= javascript_pack_tag 'hello_vue' %>
    15:   </body>
    16: </html>

app/views/layouts/application.html.erb:14
^C- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2020-11-12 02:56:13 +0000 ===
- Goodbye!

ここにも書いてあるが、→https://github.com/rails/webpacker#vue
下のコマンドを上から実行していくと、エラーが解決しました。

./bin/rails webpacker:install
./bin/rails webpacker:install:vue
bin/webpack
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails × VueでSPA開発】Vue Router・Vuexについて学ぶ

背景

未経験からエンジニア就職を目指します。良質なポートフォリオ作成のためにRails APIとvue.jsを用いたSPA開発を勉強することにしました。

現状の知識レベルとしては、Ruby on railsを使って簡単なアプリケーション開発、gitを使ったバージョン管理、herokuを使ってデプロイできるレベルです。自分の忘備録かつ、同じくらいのレベルでこれからvue.jsに挑戦してみようと思っている方に向けて少しでも役に立てればと思います。

本記事の目標

Vue Router・Vuexを理解する。

【参考】
本記事はudemyの講座『超Vue.js 2 完全パック - もう他の教材は買わなくてOK! (Vue Router, Vuex含む)』で学んだ内容のアウトプットです。なのでコード等は講座の内容がベースとなっております。

【関連記事】
【Rails × VueでSPA開発】Vue.jsの基礎を学ぶ (vue.jsの概要・template構文)
【Rails × VueでSPA開発】Vue.jsの基礎を学ぶ(vueインスタンス・コンポーネント・VueCLI)
【Rails × VueでSPA開発】Vue.jsの基礎を学ぶ(カスタムディレクティブ・トランジションなど)
【Rails × VueでSPA開発】Vue Router・Vuexについて学ぶ

目次

  1. Vue Router
  2. Vuex

それでは早速行きましょう。


1. Vue Router

VueRouterとはURLとVueを紐付けるものになります。もっと簡単にいうと「/user」のURLがきたらユーザーの一覧画面のコンポーネントを表示するといった感じです。このVue Routeの仕組を使うことによってSPAの開発が可能になります。まずインストールからしましょう。

■Vue Routerをインストールする

ターミナル
$ npm install vue-router

これだけでインストール完了です。ちなみに今回はvueのバージョンを2.6でやっています。vue3.0もリリースされましたが、教材や参考例が少ないため初心者がvue3でやるのは効率が悪いと考えたからです。また、vue2系で設定しても、後で3系にアップグレードはできるので問題はないかと思います。

またちなみに以下の記述は「vue create practice」と新しいプロジェクトを作成した上で書いています。

■Vue Routerの設定をする

router.js
import Vue from 'vue'
import Router from 'vue-router';
import Home from "./views/Home.vue";
import Users from "./views/Users.vue";

Vue.use(Router);

export default new Router({
  routes: [{path: '/', component: Home},{path: '/users', component: Users}]
})
main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";

Vue.config.productionTip = false;

new Vue({
  router,
  render: h => h(App)
}).$mount("#app");
App.vue
<template>
  <div>
    <router-view></router-view>
  </div>
</template>
views/Home.vue
<template>
  <div>
    <h1>home</h1>
  </div>
</template>
views/Users.vue
<template>
  <div>
    <h1>users</h1>
  </div>
</template>
  • Vue Routerに関する記述はどこでも問題ないですが、多くの場合「router.js」というファイルを作成して書くのが慣例ですのでそれに従います。
  • 「router.js」の記述に関して、「import Vue from 'vue'」はvueをファイル内で使えるようにする記述、「import Router from 'vue-router';」はVue Routerを「Router」として使えるようにする記述です。
  • 具体的なURLの設定は「export default new Router」の後に「routes[{path: '/', component: Home},・・・]」のように、配列にオブジェクトをいれる形で記述します。オブジェクトの中身はパス名とコンポーネント名です。
  • 「main.js」にもVue Routerを使う記述をします。というのも「index.html」が呼ばれた後に「main.js」が呼ばれて、そこでルーターを使う宣言を読み込むからです。importで「route.js」を呼び出し、vueインスタンスにrouterを記述します。
  • 「App.vue」にrouter-viewタグを挿入します。この記述によってURLごとに表示するコンポーネントを呼び出します。

以上で基本設定は終わりです。


~ 補足 ~
ここでSPAの仕組みを考えてみます。Vueでは全てのリクエストに対して「index.html」を渡します。「index.htm」lは「main.js」を呼び出し、「main.js」はURLごとに表示すべきコンポーネント情報(route.jsの内容)を持っており、それを「App.js」に渡してURLごとに表示を切り替えます。すなわち呼び出されるのは単一のページ(index.html)であり、そのあとはvue.jsによってページを切り替えるためSPA(Single Page Application)なのです。最初の読み込みさえ終わればあとはJavaScriptでページを切り替えるので高速になります。ちなみにあくまでVueRouterはURLの文字列でコンポーネントを組み合わせているだけなので、URLごと(厳密にいうとURLごとに使われるコンポーネントごと)にデータを取得するのはバックエンドの仕事になります。このバックエンドをするのがRails APIです。やっとRailsとVueによるSPA開発の全貌がやっと見えてきましたね。


Vue Routeの使い方詳細

コードを分割して説明したいところですが、あまりに量が膨大なため最終的なコードを書きます。

App.vue
<template>
  <div>
    <router-view name="header"></router-view>  <!-- /①名前付きrouter-view/ -->
    <transition name="fade" mode="out-in">    <!-- /②router-viewにトランジションをつける/ -->
      <router-view ></router-view>
    </transition>
  </div>
</template>

<style scoped>                                       
.fade-enter,
.fade-leave-to {
  opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
</style>
main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";

Vue.config.productionTip = false;

router.beforeEach((to, from, next) => {   //③beforeEachで遷移の制限
  if (toString.path === "/user/1"){
    next({path:"/"})
  }
})

new Vue({
  router,
  render: h => h(App)
}).$mount("#app");
router.js
import Vue from "vue";
import Router from "vue-router";
const Home = () => import( "./views/Home.vue");                  //④遅延ローディング設定
const Users = () => import( "./views/Users.vue");                //④遅延ローディング設定
const UsersPosts = () =>  import( "./views/UsersPosts.vue");     //④遅延ローディング設定
const UsersProfile = () =>  import("./views/UsersProfile.vue");  //④遅延ローディング設定
const HeaderHome = () =>  import( "./views/HeaderHome.vue");     //④遅延ローディング設定
const HeaderUsers = () =>  import("./views/HeaderUsers.vue");    //④遅延ローディング設定

Vue.use(Router);

export default new Router({
  mode: "history",                                               //⑤historyモードへ変更
  routes: [
    {path: '/', 
     components: {                                            //⑥①名前付きrouter-viewの内容
       default: Home,
       header: HeaderHome
     },
     beforeEnter(to ,from, next){                                //⑦特定のページの遷移の制限
       next(false);
     }
    },
    {
     path: '/users/:id',                                         //⑧動的URLの設定
     components: {
      default: Users,
      header: HeaderUser
     }, 
     props: {                                                    //⑨propsの設定
       default: true,
       header: false
     },
     children: [                                                 //⑩ネスト構造の設定
       {path: "posts", component: UsersPosts},
       {path: "profile", component: UsersProfile , name: "users-id-profile"}  //⑪パスに名前をつける
     ]},
     {                                                          //⑫リダイレクト設定
      path: "*",
      redirect: "/"
     }]
})
views/Home.vue
<template>
  <div>
    <h1>Home</h1>
    <button @click="toUsers">Usersのページに行く</button>     <!-- /⑬v-onのパス指定/ -->
  </div>
</template>

<script>
export default {
  methods: {
    toUsers() {                                              
      this.$router.push({ 
        name: "users-id-profile",
        params: { id: 1 }
      });
    }
  }
};
</script>                                                    
views/User.vue
<template>
  <div>
    <h1>users</h1>
    <h1>User No.{{id}}</h1>
  <!-- /⑭router-link・toの使い方/ -->
    <router-link to="/users/1">ユーザー1</router-link>
    <router-link :to="'/users/' + (Number(id) + 1 ) + '/profile' ">次のユーザー</router-link>
    <router-link :to="{ name: 'users-id-profile', params: { id: Number(id) + 1 }}">次のユーザー2</router-link>
    <router-view></router-view>
  </div>
</template>
                                 
<script>
export default {
  props: ["id"],
  beforeRouteEnter(to,from,next){
    next()
  },
  beforeRouteUpdate(to,from,next){
    next()
  },
  beforeRouteLeave(to,from,next){
    const isLeave = window.comfirm("本当にこのページを終了しますか?");
    if (isLeave){
      next()
    } else {
      next(false)
    }
  },
};
</script>               <!-- /⑮コンポーネントに指定できるナビゲーションガード/ -->
views/HeaderHome.vue
<template>
<div>
  <h2>home</h2>
 <!-- /⑰active-classの使い方/ -->
  <router-link to ="/" exact-active-class="link--active">Home</router-link>   
</div>
</template>

<style scoped>
.link--active{
  font-size: 20px;
}
</style>
views/HeaderUsers.vue
<template>
<div>
  <h2>Users</h2>
  <router-link to ="/users" exact-active-class="link--active">Users</router-link>
</div>
</template>

<style scoped>
.link--active{ font-size: 30px;}
</style>
views/UsersPosts.vue
<template>
  <div>
    <h2>投稿</h2>
  </div>
</template>
views/UsersProfile.vue
<template>
  <div>
    <h2>プロフィール</h2>
  </div>
</template>
  • ①「router-view」は名前をつけることができます。⑥に設定が書いてあり、defaultの場合とheaderという名前付きの場合の呼び出すコンポーネントが書いてあります。
  • ②「router-view」にはtransitionタグをつけることでトランジションやアニメーションを設定できます。
  • ③「beforeEach」でページの遷移の制限できます。引数に(to,from,next)をとることができ、nextで遷移制限を設定できます。
  • ④遅延ローディングとは必要なときに必要なページを読み込む設定です。SPAは通常初回にすべてのコンポーネントを読み込みますが、遅延ローディングを設定することで読み込みを分散させることができます。
  • ⑤historyモードとは簡単にいうとURLに「#」がつかないようにする設定です。
  • ⑦「beforeEnter」は③の「beforeEach」とほとんど同じ使い方です。後者が全てのページに対する設定なのに対して、前者は特定のページに適用します。
  • ⑧URLに「:id」と設定することで動的な値を設定できます。また⑨の「props」設定でtureとすると、「User.vue」ファイルでidと書くだけでデータを呼び出す事ができます。
  • ⑩childrenを使うとネスト構造のルーティングが設定できます。
  • ⑪パスに名前をつけることでURLの呼び出しが楽になります。
  • ⑫リダイレクトの設定です。「*」はすべてを意味し、設定したurl以外にアクセスするとルートパスへリダイレクトされます。
  • ⑬v-onでパスをしています。パスに関する設定はオブジェクトで書かれています。
  • ⑭「router-link」を使うとクリックでURLを変更できます。パスの指定の仕方はオブジェクトでも可能です。v-bindを使うと動的なURLも指定できます。ちなみにHTMLのaタグだと読み込みが毎回入ってしまうのでSPAの良さを消してしまいます。
  • ⑮コンポーネントにナビゲーションを設定できます。今回は「beforeRouteLeave」で遷移時にアラートがでるようにしています。
  • ⑯「active-class」で「route-link」に対し動的なスタイルを適用できます。また「exact-active-class」でURLの完全一致での適用が可能です。

※あとはscrollBehaviorなどスクロールに関する設定もありますが、少し複雑なので今回はなしです。


2. Vuex

Vuexとはコンポーネント間のデータ共有を簡単にするものです。アプリケーションの構成が複雑になっていくとその分コンポーネント数が増えますが、直接紐付いていないコンポーネント間でデータを共有するということは非常に難しいです。例えば親・子・孫のコンポーネントがあるとすると、孫同士のコンポーネントは子や親を経由しなければ共有できません。そんなときVuexを使うと簡単にこの問題が解決できます。

■Vuexの設定をする

ターミナル
$ npm install vuex

これでインストール完了です。一部省略していますが先程のVue Routerで設定したファイル構成に追加する形で書きます。

■Vuexの設定と基本動作

store.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vue.Store({
  state: {
    count: 2
  }
})
main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount("#app");

views/Home.vue
<template>
    <p>{{count}}</p>
</template>

<script>
export default {
  computed: {
    count(){
      return this.$store.state.count
    }
  }
</script>
views/HeaderHome.vue
<template>
<div>
  <button @click="increment">+1</button>
  <button @click="decrement">-1</button>
</div>
</template>

<script>
export default {
  methods: {
    increment(){
      this.$store.state.count++;
    },
    decrement(){
      this.$store.state.count--;
    },
  }
}
</script>
  • Vuexに関する記述もどこでも良いのですが、慣例に従い「store.js」というファイルを作成し、そこに記述します。
  • 基本の設定方法はVue Routerと基本同じです。「import Vuex from "vuex"」でインストールしたvuexをファイルないで使えるようにし、「Vue.use(Vuex)」でvuexを使うことを宣言し、「export default new Vue.Store」でvuexのインスタンスを作成します。
  • 「state」を使うことでどこからでもvuexの中身にどこからでもアクセスできるようにします。
  • 「main.js」にもVue Routerを使う記述をします。ここの記述方法や記述理由もVue Routerと全く同じです。
  • 上記での設定が完了すれば、methodでもcomputedでもvuexにアクセスすることができます。アクセスする方法は「this.$store.state.count」のようにします。「store」の部分がvuexで「state」「count」は先程「store.js」に書いた内容です。


■Vuexの使い方詳細

こちらも量が多くなりそうなので、最終的なコードを一気に記述します。

store.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vue.Store({
  state: {
    count: 2,
    message: ""
  },
  getters:{
    doubleCount: state => state.count * 2,     //①getterの記述
    tripleCount: state => state.count * 3,
    message: state => state.message
  },
  mutations: {                                 //②mutationの記述
    increment(state, number) {
      state.count += number
    },
    decrement(state, number) {
      state.count -= number
    },
    updateMessage (state, newMessage){
      state.message = newMessage;
    }
  },
  actions: {                                    //③actionの記述
    increment({commit}, number) {
      commit('increment', number);
    },
    decrement({commit}, number){
      commit('decrement', number);
    },
    updateMessage({commit}, newMessage) {
      commit('updateMessage', number);
    },
  }
})
Home.vue
<template>
  <div>
    <p>{{count}}</p>
    <h1>Home</h1>
    <button @click="toUsers">Usersのページに行く</button>
    <input type="text" v-model="message">
  </div>
</template>

<script>
import {mapGetters} from "vuex";           

export default {
  computed: {
    ...mapGetters(["doubleCount", "tripleCount"]),    //④gettersの呼び出し
    message: {                                        //⑤ゲッターとセッターの設定
      get(){
        return this.$store.getters.message;
      },
      set(value){
        this.$store.dispatch("updateMessage" , value);
      }
    }
  },
  methods: {
    toUsers() {
      this.$router.push({
        name: "users-id-profile",
        params: { id: 1 }
      });
    }
  }
};
</script>
HeaderHome.vue
<template>
<div>
  <h2>home</h2>
  <router-link to ="/">Home</router-link>  
  <button @click="increment(2)">+1</button>
  <button @click="decrement(2)">-1</button>
</div>
</template>

<script>
import { mapActions } from "vuex";          //⑥actionの呼び出し
export default {
  // methods:{
  //   increment(){
  //     this.$store.dispatch("increment",2);
  //   },
  //   decrement(){
  //     this.$store.dispatch("decrement",2);
  //   },
  // }
    methods: {
      ...mapActions(["increment", "decrement"])
   }
};
</script>
  • ① getterとは算出プロフパティを指します。先程の基本動作のときは例として、computedやmethodにvuexから呼び出した値を取得して計算等していましたが、その計算にあたる処理を「store.js」にかけるようにしたものがgetterです。getterで記述したほうが管理がしやすいので、vuexを使う際はgetterに全てまとめるようにするのが良いです。
  • ② mutationとはstateの値(今回でいうとcountとmessage)を変更する場所を1箇所にする設定です。アプリケーションが膨大になっていくとなると、どこからvuexが呼ばれ、vuexの値がどこで書き換えられたかわからなくなります。そこでmutationだけに値を変更する記述することで、この混乱を防げます。使い方は第1引数にstate、第2引数に代入したい値名をいれます。またmutationを呼び出すにはcommitメソッドを使います。
  • ③actionsとは非同期的なやり取りを含む処理を記述する際に用います。というのもvuexは基本的に同期的なやりとりをするからです。今回は第1引数にcommit(mutationを経由するため)、第2引数に代入したい値名をいれます。
  • ④「mapGeters」を使うと簡単に複数のgetterを呼び出せます。ちなみにcomputedに「mapGeters」以外に複数使いたい場合、「...(スプレッド演算子)」をいれることで対応できるようになります。
  • ⑤ゲッターとセッターを設定することでv-modelによる双方向バインディングをvuexで適用できます。
  • ⑥actionも「mapActions」を使うと簡単に複数のactionを呼び出せます。またactionの呼び出しはコメントアウトしている行のようにdispatchを使うことが基本です。

※名前空間を使いモジュール間で同じ名前を使う方法、vuexのファイルを分割して管理しやすくする方法もありますが今回は省略します。



以上で今回は終わりです。そしてやっと次回からRailsが入ってきます。楽しみです。railsとvueを連携するにはaxiosというものを使うのですが、それもおいおい説明できればと思います。


まとめ・感想

今回は本当に勉強していて面白かったです。というのも今までは文法チックで本当にこれでアプリケーションが開発できるかわからなかったですが、今回のVue RouterやVuexでSPA開発の具体的なイメージが湧いたからです。Railsの開発久々で心配ですが頑張っていきたいです。


参考

【udemy】
超Vue.js 2 完全パック - もう他の教材は買わなくてOK! (Vue Router, Vuex含む)
→おすすめです!本記事もこちらの講座で習ったことがベースです。基礎から丁寧に解説してくれていますし、SPA開発にまで触れているのでこれだけでvueの基本はカバーできます。

Vue JS入門決定版!jQuery を使わない Web 開発 - 導入からアプリケーション開発まで体系的に動画で学ぶ
→文法に関して詳しく説明していて良いと思いました。

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

【Rails × VueでSPA開発】Vue Router・Vuexを学ぶ

背景

未経験からエンジニア就職を目指します。良質なポートフォリオ作成のためにRails APIとvue.jsを用いたSPA開発を勉強することにしました。

現状の知識レベルとしては、Ruby on railsを使って簡単なアプリケーション開発、gitを使ったバージョン管理、herokuを使ってデプロイできるレベルです。自分の忘備録かつ、同じくらいのレベルでこれからvue.jsに挑戦してみようと思っている方に向けて少しでも役に立てればと思います。

本記事の目標

Vue Router・Vuexを理解する。

【参考】
本記事はudemyの講座『超Vue.js 2 完全パック - もう他の教材は買わなくてOK! (Vue Router, Vuex含む)』で学んだ内容のアウトプットです。なのでコード等は講座の内容がベースとなっております。

【関連記事】
【Rails × VueでSPA開発】Vue.jsの基礎を学ぶ (vue.jsの概要・template構文)
【Rails × VueでSPA開発】Vue.jsの基礎を学ぶ(vueインスタンス・コンポーネント・VueCLI)
【Rails × VueでSPA開発】Vue.jsの基礎を学ぶ(カスタムディレクティブ・トランジションなど)
【Rails × VueでSPA開発】Vue Router・Vuexについて学ぶ

目次

  1. Vue Router
  2. Vuex

それでは早速行きましょう。


1. Vue Router

VueRouterとはURLとVueを紐付けるものになります。もっと簡単にいうと「/user」のURLがきたらユーザーの一覧画面のコンポーネントを表示するといった感じです。このVue Routeの仕組を使うことによってSPAの開発が可能になります。まずインストールからしましょう。

■Vue Routerをインストールする

ターミナル
$ npm install vue-router

これだけでインストール完了です。ちなみに今回はvueのバージョンを2.6でやっています。vue3.0もリリースされましたが、教材や参考例が少ないため初心者がvue3でやるのは効率が悪いと考えたからです。また、vue2系で設定しても、後で3系にアップグレードはできるので問題はないかと思います。

またちなみに以下の記述は「vue create practice」と新しいプロジェクトを作成した上で書いています。

■Vue Routerの設定をする

router.js
import Vue from 'vue'
import Router from 'vue-router';
import Home from "./views/Home.vue";
import Users from "./views/Users.vue";

Vue.use(Router);

export default new Router({
  routes: [{path: '/', component: Home},{path: '/users', component: Users}]
})
main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";

Vue.config.productionTip = false;

new Vue({
  router,
  render: h => h(App)
}).$mount("#app");
App.vue
<template>
  <div>
    <router-view></router-view>
  </div>
</template>
views/Home.vue
<template>
  <div>
    <h1>home</h1>
  </div>
</template>
views/Users.vue
<template>
  <div>
    <h1>users</h1>
  </div>
</template>
  • Vue Routerに関する記述はどこでも問題ないですが、多くの場合「router.js」というファイルを作成して書くのが慣例ですのでそれに従います。
  • 「router.js」の記述に関して、「import Vue from 'vue'」はvueをファイル内で使えるようにする記述、「import Router from 'vue-router';」はVue Routerを「Router」として使えるようにする記述です。
  • 具体的なURLの設定は「export default new Router」の後に「routes[{path: '/', component: Home},・・・]」のように、配列にオブジェクトをいれる形で記述します。オブジェクトの中身はパス名とコンポーネント名です。
  • 「main.js」にもVue Routerを使う記述をします。というのも「index.html」が呼ばれた後に「main.js」が呼ばれて、そこでルーターを使う宣言を読み込むからです。importで「route.js」を呼び出し、vueインスタンスにrouterを記述します。
  • 「App.vue」にrouter-viewタグを挿入します。この記述によってURLごとに表示するコンポーネントを呼び出します。

以上で基本設定は終わりです。


~ 補足 ~
ここでSPAの仕組みを考えてみます。Vueでは全てのリクエストに対して「index.html」を渡します。「index.htm」lは「main.js」を呼び出し、「main.js」はURLごとに表示すべきコンポーネント情報(route.jsの内容)を持っており、それを「App.js」に渡してURLごとに表示を切り替えます。すなわち呼び出されるのは単一のページ(index.html)であり、そのあとはvue.jsによってページを切り替えるためSPA(Single Page Application)なのです。最初の読み込みさえ終わればあとはJavaScriptでページを切り替えるので高速になります。ちなみにあくまでVueRouterはURLの文字列でコンポーネントを組み合わせているだけなので、URLごと(厳密にいうとURLごとに使われるコンポーネントごと)にデータを取得するのはバックエンドの仕事になります。このバックエンドをするのがRails APIです。やっとRailsとVueによるSPA開発の全貌がやっと見えてきましたね。


Vue Routeの使い方詳細

コードを分割して説明したいところですが、あまりに量が膨大なため最終的なコードを書きます。

App.vue
<template>
  <div>
    <router-view name="header"></router-view>  <!-- /①名前付きrouter-view/ -->
    <transition name="fade" mode="out-in">    <!-- /②router-viewにトランジションをつける/ -->
      <router-view ></router-view>
    </transition>
  </div>
</template>

<style scoped>                                       
.fade-enter,
.fade-leave-to {
  opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
</style>
main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";

Vue.config.productionTip = false;

router.beforeEach((to, from, next) => {   //③beforeEachで遷移の制限
  if (toString.path === "/user/1"){
    next({path:"/"})
  }
})

new Vue({
  router,
  render: h => h(App)
}).$mount("#app");
router.js
import Vue from "vue";
import Router from "vue-router";
const Home = () => import( "./views/Home.vue");                  //④遅延ローディング設定
const Users = () => import( "./views/Users.vue");                //④遅延ローディング設定
const UsersPosts = () =>  import( "./views/UsersPosts.vue");     //④遅延ローディング設定
const UsersProfile = () =>  import("./views/UsersProfile.vue");  //④遅延ローディング設定
const HeaderHome = () =>  import( "./views/HeaderHome.vue");     //④遅延ローディング設定
const HeaderUsers = () =>  import("./views/HeaderUsers.vue");    //④遅延ローディング設定

Vue.use(Router);

export default new Router({
  mode: "history",                                               //⑤historyモードへ変更
  routes: [
    {path: '/', 
     components: {                                            //⑥①名前付きrouter-viewの内容
       default: Home,
       header: HeaderHome
     },
     beforeEnter(to ,from, next){                                //⑦特定のページの遷移の制限
       next(false);
     }
    },
    {
     path: '/users/:id',                                         //⑧動的URLの設定
     components: {
      default: Users,
      header: HeaderUser
     }, 
     props: {                                                    //⑨propsの設定
       default: true,
       header: false
     },
     children: [                                                 //⑩ネスト構造の設定
       {path: "posts", component: UsersPosts},
       {path: "profile", component: UsersProfile , name: "users-id-profile"}  //⑪パスに名前をつける
     ]},
     {                                                          //⑫リダイレクト設定
      path: "*",
      redirect: "/"
     }]
})
views/Home.vue
<template>
  <div>
    <h1>Home</h1>
    <button @click="toUsers">Usersのページに行く</button>     <!-- /⑬v-onのパス指定/ -->
  </div>
</template>

<script>
export default {
  methods: {
    toUsers() {                                              
      this.$router.push({ 
        name: "users-id-profile",
        params: { id: 1 }
      });
    }
  }
};
</script>                                                    
views/User.vue
<template>
  <div>
    <h1>users</h1>
    <h1>User No.{{id}}</h1>
  <!-- /⑭router-link・toの使い方/ -->
    <router-link to="/users/1">ユーザー1</router-link>
    <router-link :to="'/users/' + (Number(id) + 1 ) + '/profile' ">次のユーザー</router-link>
    <router-link :to="{ name: 'users-id-profile', params: { id: Number(id) + 1 }}">次のユーザー2</router-link>
    <router-view></router-view>
  </div>
</template>
                                 
<script>
export default {
  props: ["id"],
  beforeRouteEnter(to,from,next){
    next()
  },
  beforeRouteUpdate(to,from,next){
    next()
  },
  beforeRouteLeave(to,from,next){
    const isLeave = window.comfirm("本当にこのページを終了しますか?");
    if (isLeave){
      next()
    } else {
      next(false)
    }
  },
};
</script>               <!-- /⑮コンポーネントに指定できるナビゲーションガード/ -->
views/HeaderHome.vue
<template>
<div>
  <h2>home</h2>
 <!-- /⑰active-classの使い方/ -->
  <router-link to ="/" exact-active-class="link--active">Home</router-link>   
</div>
</template>

<style scoped>
.link--active{
  font-size: 20px;
}
</style>
views/HeaderUsers.vue
<template>
<div>
  <h2>Users</h2>
  <router-link to ="/users" exact-active-class="link--active">Users</router-link>
</div>
</template>

<style scoped>
.link--active{ font-size: 30px;}
</style>
views/UsersPosts.vue
<template>
  <div>
    <h2>投稿</h2>
  </div>
</template>
views/UsersProfile.vue
<template>
  <div>
    <h2>プロフィール</h2>
  </div>
</template>
  • ①「router-view」は名前をつけることができます。⑥に設定が書いてあり、defaultの場合とheaderという名前付きの場合の呼び出すコンポーネントが書いてあります。
  • ②「router-view」にはtransitionタグをつけることでトランジションやアニメーションを設定できます。
  • ③「beforeEach」でページの遷移の制限できます。引数に(to,from,next)をとることができ、nextで遷移制限を設定できます。
  • ④遅延ローディングとは必要なときに必要なページを読み込む設定です。SPAは通常初回にすべてのコンポーネントを読み込みますが、遅延ローディングを設定することで読み込みを分散させることができます。
  • ⑤historyモードとは簡単にいうとURLに「#」がつかないようにする設定です。
  • ⑦「beforeEnter」は③の「beforeEach」とほとんど同じ使い方です。後者が全てのページに対する設定なのに対して、前者は特定のページに適用します。
  • ⑧URLに「:id」と設定することで動的な値を設定できます。また⑨の「props」設定でtureとすると、「User.vue」ファイルでidと書くだけでデータを呼び出す事ができます。
  • ⑩childrenを使うとネスト構造のルーティングが設定できます。
  • ⑪パスに名前をつけることでURLの呼び出しが楽になります。
  • ⑫リダイレクトの設定です。「*」はすべてを意味し、設定したurl以外にアクセスするとルートパスへリダイレクトされます。
  • ⑬v-onでパスをしています。パスに関する設定はオブジェクトで書かれています。
  • ⑭「router-link」を使うとクリックでURLを変更できます。パスの指定の仕方はオブジェクトでも可能です。v-bindを使うと動的なURLも指定できます。ちなみにHTMLのaタグだと読み込みが毎回入ってしまうのでSPAの良さを消してしまいます。
  • ⑮コンポーネントにナビゲーションを設定できます。今回は「beforeRouteLeave」で遷移時にアラートがでるようにしています。
  • ⑯「active-class」で「route-link」に対し動的なスタイルを適用できます。また「exact-active-class」でURLの完全一致での適用が可能です。

※あとはscrollBehaviorなどスクロールに関する設定もありますが、少し複雑なので今回はなしです。


2. Vuex

Vuexとはコンポーネント間のデータ共有を簡単にするものです。アプリケーションの構成が複雑になっていくとその分コンポーネント数が増えますが、直接紐付いていないコンポーネント間でデータを共有するということは非常に難しいです。例えば親・子・孫のコンポーネントがあるとすると、孫同士のコンポーネントは子や親を経由しなければ共有できません。そんなときVuexを使うと簡単にこの問題が解決できます。

■Vuexの設定をする

ターミナル
$ npm install vuex

これでインストール完了です。一部省略していますが先程のVue Routerで設定したファイル構成に追加する形で書きます。

■Vuexの設定と基本動作

store.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vue.Store({
  state: {
    count: 2
  }
})
main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount("#app");

views/Home.vue
<template>
    <p>{{count}}</p>
</template>

<script>
export default {
  computed: {
    count(){
      return this.$store.state.count
    }
  }
</script>
views/HeaderHome.vue
<template>
<div>
  <button @click="increment">+1</button>
  <button @click="decrement">-1</button>
</div>
</template>

<script>
export default {
  methods: {
    increment(){
      this.$store.state.count++;
    },
    decrement(){
      this.$store.state.count--;
    },
  }
}
</script>
  • Vuexに関する記述もどこでも良いのですが、慣例に従い「store.js」というファイルを作成し、そこに記述します。
  • 基本の設定方法はVue Routerと基本同じです。「import Vuex from "vuex"」でインストールしたvuexをファイルないで使えるようにし、「Vue.use(Vuex)」でvuexを使うことを宣言し、「export default new Vue.Store」でvuexのインスタンスを作成します。
  • 「state」を使うことでどこからでもvuexの中身にどこからでもアクセスできるようにします。
  • 「main.js」にもVue Routerを使う記述をします。ここの記述方法や記述理由もVue Routerと全く同じです。
  • 上記での設定が完了すれば、methodでもcomputedでもvuexにアクセスすることができます。アクセスする方法は「this.$store.state.count」のようにします。「store」の部分がvuexで「state」「count」は先程「store.js」に書いた内容です。


■Vuexの使い方詳細

こちらも量が多くなりそうなので、最終的なコードを一気に記述します。

store.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vue.Store({
  state: {
    count: 2,
    message: ""
  },
  getters:{
    doubleCount: state => state.count * 2,     //①getterの記述
    tripleCount: state => state.count * 3,
    message: state => state.message
  },
  mutations: {                                 //②mutationの記述
    increment(state, number) {
      state.count += number
    },
    decrement(state, number) {
      state.count -= number
    },
    updateMessage (state, newMessage){
      state.message = newMessage;
    }
  },
  actions: {                                    //③actionの記述
    increment({commit}, number) {
      commit('increment', number);
    },
    decrement({commit}, number){
      commit('decrement', number);
    },
    updateMessage({commit}, newMessage) {
      commit('updateMessage', number);
    },
  }
})
Home.vue
<template>
  <div>
    <p>{{count}}</p>
    <h1>Home</h1>
    <button @click="toUsers">Usersのページに行く</button>
    <input type="text" v-model="message">
  </div>
</template>

<script>
import {mapGetters} from "vuex";           

export default {
  computed: {
    ...mapGetters(["doubleCount", "tripleCount"]),    //④gettersの呼び出し
    message: {                                        //⑤ゲッターとセッターの設定
      get(){
        return this.$store.getters.message;
      },
      set(value){
        this.$store.dispatch("updateMessage" , value);
      }
    }
  },
  methods: {
    toUsers() {
      this.$router.push({
        name: "users-id-profile",
        params: { id: 1 }
      });
    }
  }
};
</script>
HeaderHome.vue
<template>
<div>
  <h2>home</h2>
  <router-link to ="/">Home</router-link>  
  <button @click="increment(2)">+1</button>
  <button @click="decrement(2)">-1</button>
</div>
</template>

<script>
import { mapActions } from "vuex";          //⑥actionの呼び出し
export default {
  // methods:{
  //   increment(){
  //     this.$store.dispatch("increment",2);
  //   },
  //   decrement(){
  //     this.$store.dispatch("decrement",2);
  //   },
  // }
    methods: {
      ...mapActions(["increment", "decrement"])
   }
};
</script>
  • ① getterとは算出プロフパティを指します。先程の基本動作のときは例として、computedやmethodにvuexから呼び出した値を取得して計算等していましたが、その計算にあたる処理を「store.js」にかけるようにしたものがgetterです。getterで記述したほうが管理がしやすいので、vuexを使う際はgetterに全てまとめるようにするのが良いです。
  • ② mutationとはstateの値(今回でいうとcountとmessage)を変更する場所を1箇所にする設定です。アプリケーションが膨大になっていくとなると、どこからvuexが呼ばれ、vuexの値がどこで書き換えられたかわからなくなります。そこでmutationだけに値を変更する記述することで、この混乱を防げます。使い方は第1引数にstate、第2引数に代入したい値名をいれます。またmutationを呼び出すにはcommitメソッドを使います。
  • ③actionsとは非同期的なやり取りを含む処理を記述する際に用います。というのもvuexは基本的に同期的なやりとりをするからです。今回は第1引数にcommit(mutationを経由するため)、第2引数に代入したい値名をいれます。
  • ④「mapGeters」を使うと簡単に複数のgetterを呼び出せます。ちなみにcomputedに「mapGeters」以外に複数使いたい場合、「...(スプレッド演算子)」をいれることで対応できるようになります。
  • ⑤ゲッターとセッターを設定することでv-modelによる双方向バインディングをvuexで適用できます。
  • ⑥actionも「mapActions」を使うと簡単に複数のactionを呼び出せます。またactionの呼び出しはコメントアウトしている行のようにdispatchを使うことが基本です。

※名前空間を使いモジュール間で同じ名前を使う方法、vuexのファイルを分割して管理しやすくする方法もありますが今回は省略します。



以上で今回は終わりです。そしてやっと次回からRailsが入ってきます。楽しみです。railsとvueを連携するにはaxiosというものを使うのですが、それもおいおい説明できればと思います。


まとめ・感想

今回は本当に勉強していて面白かったです。というのも今までは文法チックで本当にこれでアプリケーションが開発できるかわからなかったですが、今回のVue RouterやVuexでSPA開発の具体的なイメージが湧いたからです。Railsの開発久々で心配ですが頑張っていきたいです。


参考

【udemy】
超Vue.js 2 完全パック - もう他の教材は買わなくてOK! (Vue Router, Vuex含む)
→おすすめです!本記事もこちらの講座で習ったことがベースです。基礎から丁寧に解説してくれていますし、SPA開発にまで触れているのでこれだけでvueの基本はカバーできます。

Vue JS入門決定版!jQuery を使わない Web 開発 - 導入からアプリケーション開発まで体系的に動画で学ぶ
→文法に関して詳しく説明していて良いと思いました。

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

Vue.js 3.0 新機能 Composition APIの書き方 従来との比較

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

Vue.js 3.0 新機能 Composition APIの書き方 変更点まとめ

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

Vue.jsでArrayのpropsをwatchする方法

親コンポーネントから子コンポーネントにArrayのPropsを渡す際に、子コンポーネントのwatch関数で変更を検知したい。

コード

watch関数にdeepプロパティを追加する。

export default {
  props: {
    arrayProps: {
      type: Array,
      require: false
    }
  },

  watch: {
    arrayProps: {
      handler (val) {
        console.log(val)
      },
      deep: true // 追記
    }
  }
}

ドキュメント

watch

おまけ

watch関数にはdeepプロパティの他にimmediateがある。
immediateは、描画時の最初に実行してくれる。監視するけど描画時にも一度実行しておきたい場合に使えそう。

immediateは「即時」の意味。

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

【保存版】Vue.jsのフォーム作成にて簡単バリデーションを実装

はじめに

本記事へのアクセスありがとうございます。
投稿主はプログラミング初心者であり、この方法が「最適解」かは分かりません。
しかし、動作は検証済みです。

こんなやり方もできるんだ〜程度に見てもらえれば幸いです。

さっそくスタート

Vueにおいて1からバリデーションを実装しよう思うと初心者には大変なので、今回はVuelidateというライブラリを使用します。
他にもVeeValidateというライブラリも有名なので、本記事が上手くいかない、納得いかない方はそちらを試してみることをオススメします。

では、まずVuelidateをダウンロードするために下記のコードを実装します。
この記事ではyarnでダウンロードを記載していきますが、npmの方はnpmのダウンロード方法でお願いします。

yarn add --dev vuelidate 

webpackのentry先であるjs(ここではmain.js)に下記のコードを追記します。

import Vuelidate from 'vuelidate';
Vue.use(Vuelidate);

これで設定は完了です。
さっそく簡単なフォームを作成して、そこにvuelidateを使用していきます。

<div>
  <input 
   type="text"
   class="vInput"
   v-model="validationInput"
   @blur="$v.validationInput.$touch()"
   :class="{ error: $v.validationInput.$error, vInput: true }"
   />
  <select
    class="vSelect"
    v-model="validationSelect"
    @change="$v.validationSelect.$touch()"
    :class="{ error: $v.validationSelect.$error, vSelect: true }"
        >
    <option disabled value="">選択してください</option>
    <option v-for="item in items" :key="item.index" :value="item.value">
       {{ item.text }}
    </option>
   </select>
   <button @click="post">POST!</button>
</div>
import { required } from "vuelidate/lib/validators";
 data() {
    return {
      validationInput: "",
      validationSelect: "",
      items: [
        { text: "サーモン", value: "sushi" },
        { text: "醤油ラーメン", value: "ramen" },
        { text: "インドカレー", value: "carry" },
        { text: "ハーゲンダッツ", value: "ice cream" },
      ],
    };
  },
  validations: {
    validationInput: {
      required,
    },
    validationSelect: {
      required,
    },
  },
 methods: {
   post() {
    this.$v.$touch();
    if (this.$v.$invalid) {
        console.log("バリデーションエラー");
      } else {
        実行したい処理
      }
    }
  }

.error {
  color: #8a0421;
  border-color: #dd0f3b;
  background-color: #ffd9d9;
}

上記のコードをすごく簡単に説明します。
1. POST!ボタンをクリックして、上記のフォーム(inputタグまたはselectタグ)で記載忘れしていると、処理が走らないようになっています。
2. styleで.errorを記載しているので、エラーになった時に赤枠で囲まれるようになっています。
3. @blur〜〜はテキストを全て入力後の判定して用いられるが、一文字一文字に判定をさせたい時は@input〜〜を用いる事で代用できます。個人的には@blur〜〜を用いた方がユーザーにとって使いやすいフォームになると思います。

上記のコードでは最低限の事しか扱っていませんが、上限文字数 / 下限文字数を設定したり色々なオプションを設定できるので、より複雑な設定をしたい方は一度公式ホームページのドキュメントに目を通す事をオススメします。

⬇︎公式ホームページ
https://vuelidate.js.org/#sub-package-content

オプション設定の参考までに最大文字数を設定する時は下記のようにします。

import { required, maxLength } from "vuelidate/lib/validators";
validationInput: {
      required,
      maxLength: maxLength(100),
    },

おわり

お疲れ様でした。これでVue.jsおけるvuelidateを用いたバリデーション設定の完了です。

少しでも役に立ったと思う方がいましたらLGTMをお願いします?‍♂️

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

Vue.js単体テストで複数のコンポーネントを扱う

単体テストで、親子揃って初めて機能するコンポーネントをテストする際などに子をマウントする方法です。

以下のような場合、親をマウントして子をコンポーネントとして登録します。

index.vue
<template>
  <tabs>
    <tab>タブ 1</tab>
    <tab>タブ 2</tab>
    <tab>タブ 3</tab>
  </tabs>
</template>
__tests__/index.spec.js
import { mount } from '@vue/test-utils';
import Tabs from '../index.vue';
import Tab from '../../Tab/index.vue';

describe('Tabs', () => {
  const wrapper = mount(Tabs, {
    slots: {
      default: [
        '<tab>タブ 1</tab>',
        '<tab>タブ 2</tab>`',
        '<tab>タブ 3</tab>',
      ]
    },
    components: {
      tab
    }
  });
});

普通に<script>内に書くようにcomponentsで登録すれば良いようです。
componentsはマウンティングオプションに記載がありませんでした。
https://vue-test-utils.vuejs.org/ja/api/options.html

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