コンテンツにスキップ

Vue.js@フレームワーク

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. Vue.jsを使用したMVVMアーキテクチャ

MVVMアーキテクチャ、双方向データバインディング

▼ MVVMアーキテクチャとは

▼ MVVMアーキテクチャにおける各層の責務

(1)

View層 (foo.html/foo.twigfoo-component.vuetemplateタグ部分)

ViewModel層から渡されたデータを出力するだけ。

(2)

ViewModel層 (index.jsfoo-component.vuescriptタグ部分)

プレゼンテーションロジック (フォーマット整形、バリデーション、Webページのローディング、エラーハンドリング、イベント発火など) や、ビジネスロジック (※控えめに) を記述する。

scriptタグによって、JavaScriptが組み込まれている。

(3)

Model層 (store.jsまたはfoo.js)

ビジネスロジックや、ajaxメソッドによるデータ送受信、を記述する。

▼ Vueを使用したMVVMアーキテクチャ、双方向データバインディング

Vueは、アプリケーションの設計にMVVMアーキテクチャを使用することを前提として、双方向データバインディングを実現できるような機能を提供する。

(1)

View層では、foo.html/foo.twigfoo-component.vuetemplateタグ部分)

(2)

ViewModel層では、index.jsfoo-component.vuescriptタグ部分

(3)

Model層では、Vuex (store.js)やJavaScriptからなるモデル (foo.js) を配置する。

(4)

これの元、双方向データバインディングが実現される仕組みとして、View層でイベントが起こると、ViewModel層でこれにバインディングされたイベントハンドラ関数がコールされる。

Vueコンポーネントツリーにおけるコンポーネント間の通信

親子コンポーネント間のデータ渡し

▼ 親子コンポーネント間のデータ渡しの仕組み (Props Down, Events Up)

まず、双方向データバインディングとは異なる概念なため、混乱しないように注意する。

コンポーネント (foo-component.vue) のscriptタグ部分 (ViewModel層) の親子間では、props$emitメソッドを使用して、データを渡す。

この仕組みを、Props Down, Events Upという。

親子コンポーネント間の双方向データバインディング

component-tree_communication


MVVMアーキテクチャの実装例

(1) 【View層】テンプレート (foo.htmlfoo.twig)

データが、テンプレートからjsファイルに渡される仕組みは、フレークワークを使用しない場合と同じである。

データがjsファイルに渡される状況としては、イベント発火時である。

*実装例*

例えば、テンプレートの親コンポーネントタグでクリックイベントが発火した時、親コンポーネントから、イベントに紐付いたイベントハンドラ関数がコールされる。

<!-- divタグのidは『app』とする -->
<div id="app">

    <!--
    ・親コンポーネントタグを記述。
    ・dataオプションの値をpropsに渡すように設定。
    ・イベントとイベントハンドラ関数を対応づける。
    -->
    <v-foo-component-1
        :criteria="criteria"
        v-on change="changeQuery"
    ></v-foo-component-1>

    <!-- 親コンポーネントタグを記述 -->
    <v-foo-component-2

    ></v-foo-component-2>

    <!-- 親コンポーネントタグを記述 -->
    <v-foo-component-3

    ></v-foo-component-3>

</div>

<!-- ルートVueインスタンスの作成は外部スクリプトで実行する。 -->
<script
    <src={{ asset(".../index.js") }}>
</script>

(1-2) 【ViewModel層】データの初期化を実行するVueコンストラクタ関数 (index.js)

vue-instance

Vueコンストラクタ関数を使用して、インスタンス化することによって、ルートVueインスタンスが作成される。

インスタンスの変数名vmはVIewModelの意味である。

インスタンス化時、全てのコンポーネントのデータが初期化される。

各コンポーネントで個別に状態を変化させたいものは、propsオプションではなく、dataオプションとして扱う。

*実装例*

// ルートVueインスタンス
// 変数に対する格納を省略しても良い
var vm = new Vue({
  // Vueインスタンスを使用するdivタグを設定.
  el: "#app",
  // dataオプション
  // Vueインスタンスのデータを保持する
  // 異なる場所にある同じコンポーネントは異なるVueインスタンスからなる。
  // 異なるVueインスタンスは異なる値を持つ必要があるため、メソッドとして定義。
  data: function () {
    return {
      isLoading: "false",
      staffData: [],
      criteria: {
        id: null,
        name: null,
      },
    };
  },

  // methodオプション
  // Vueインスタンスのアクセサメソッドや状態変化メソッド
  // イベントハンドラ関数、dataオプションのセッターを定義
  method: {
    // イベントハンドラ関数
    changeQuery(criteriaObj) {
      const keys = ["criteria", "limit"];
      for (const key of keys) {
        if (key in criteriaObj) {
          this[key] = criteriaObj[key];
        }
      }
    },

    // dataオプションのセッター
    load(query) {
      // ここでのthisはdataオプションを指す。
      this.isLoading = true;
      this.staffData = [];
      // ajaxメソッドをラッピングしたメソッドをコール
      return (
        Staff.find(query)

          // done()
          // ajaxメソッドによって返却されたJSONが引数になる。
          .done((data) => {
            // サーバーサイドからのJSONをデシリアライズ。
            // dataオプションに設定
            this.staffData = _.map(data.staffData, Staff.deserializeStaff);
          })
      );
    },
  },

  // watchオプション
  // Vueインスタンス内の値の変化を監視する関数を定義。
  // vue-routerも参考にせよ。
  watch: {},

  // テンプレートと親コンポーネントの対応になるようにする。
  component: {
    //『HTMLでのコンポーネントのタグ名:子コンポーネント』
    "v-foo-component-1": require(".../component/foo-1.vue"),
    "v-foo-component-2": require(".../component/foo-2.vue"),
    "v-foo-component-3": require(".../component/foo-3.vue"),
  },
});

(2) 【View + ViewModel層】単一ファイルコンポーネントに相当するコンポーネント (foo-component.vue)

コンポーネントは、View層に相当するtemplateタグ、ViewModel層に相当するscriptタグとstyleタグを使用して、単一ファイルコンポーネントとする。

*実装例*

例えば、親コンポーネントの子コンポーネントタグでクリックイベントが発火した時、子コンポーネントから、イベントに紐付いたイベントハンドラ関数がコールされる。

<template>
  <!----------------------------------------
  // View層
  // ・親コンポーネント
  // ・ここに、出力したいHTMLやTWIGを記述する。 
  ------------------------------------------>

  <!-- 
  ・子コンポーネントタグを記述 
  ・下方のdataオプションの値をpropsに渡すように設定。
  -->
  <v-foo-component-4 :aaa="a" :bbb="b"></v-foo-component-4>

  <!-- 条件付きレンダリングを実行するディレクション -->
  <template v-if="foo.isOk()">
    <!-- 孫コンポーネントタグを記述 -->
    <v-foo-component-5 :ccc="c" :ddd="d"></v-foo-component-5>
  </template>
</template>

<script>
  //=============
  // ViewModel層
  //=============

  // 親コンポーネント以降では、Vueインスタンスを作成しないようにする。
  module.exports = {
    // propsオプション
    // 親コンポーネントまたはajaxメソッドからpropsオブジェクトのプロパティに値が格納される。
    props: {
      criteria: {
        type: Object,
        required: "true",
      },

      foo: {
        type: Object,
        required: "true",
      },
    },

    // dataオプション
    // 異なる場所にある同じコンポーネントは異なるVueインスタンスからなる。
    // 異なるVueインスタンスは異なる値を持つ必要があるため、メソッドとして定義する。
    data: function () {
      return {
        a: "a",
        b: "b",
        c: "c",
        d: "d",
      };
    },

    // イベントハンドラ関数、dataオプションのセッター
    method: {
      updateCriteria(key, value) {
        /*
        ・コンポーネント (v-foo-component-1) と紐付く処理
        ・changeイベントの発火と、これのイベントハンドラ関数に引数を渡す。
        */
        this.$emit("change", {criteria: localCriteria});
      },

      // ajaxメソッドから受信したデータを使用して、propsを更新
      updateProps(key, value) {},

      // 『HTMLでのコンポーネントのタグ名: JSでのコンポーネントのオブジェクト名』
      component: {
        // 子コンポーネントと孫コンポーネントの対応関係
        "v-foo-component-4": require("./xxx/xxx/foo-4"),
        "v-foo-component-5": require("./xxx/xxx/foo-5"),
      },
    },
  };
</script>

(3) 【Model層】オブジェクトに相当するVuex (store.js)

ノート内の『Vuex』の項目を参照せよ。

(3-2) 【Model層】オブジェクトに相当するJavaScript (foo.js)

クラス宣言で実装する。

*実装例*

class Foo {
  /*
    ・コンポーネントからパケットを受信。


    ・プロパティの宣言と、同時に格納。


    */
  constructor(properties) {
    this.isOk = properties.isOk;
  }

  // コンストラクタによって宣言されているため、アクセスできる。
  isOk() {
    // boolean値を返却する例を考える。
    return this.isOk;
  }
}


02. View層とViewModel層の間での双方向データバインディングの方法

イベントハンドリング

v-on:とは

Vueにおけるemitとv-onの連携

View層 (templateタグ部分) のイベントを、ViewModel層 (scriptタグ部分) のイベントハンドラ関数 (methods:内にあるメソッド) やインラインJSステートメントにバインディングし、イベントが発火した時点でイベントハンドラ関数をコールする。

コンポーネントのscriptタグ部分 (ViewModel層) の親子間データ渡しである『Props Down, Events Up』とは異なる概念なので注意する。

v-on:{イベント名}="{イベントハンドラ関数 (methods: 内にあるメソッド) }"

または、省略して、

@:{イベント名}="<イベントハンドラ関数>"

で記述する。

v-on:submit="<イベントハンドラ関数>"buttonタグ

View層のフォーム送信イベントが起きた時点で、ViewModel層にバインディングされたイベントハンドラ関数をコールする。

例えば、親コンポーネントでは、子コンポーネントによって発火させられるsearchイベントに対して、resultメソッドというイベントハンドラ関数を紐付けておく。

*実装例*

<div id="app">
  <v-foo-component
          v-on:search="result()"
  ></v-foo-component>
</div>

<!-- Vueインスタンスの作成は外部スクリプトで実行する。 -->
<script src="{{ asset(".../index.js") }}">
</script>

index.jsのmethods:内には、イベントハンドラ関数としてresultメソッドを定義する。

*実装例*

// 変数に対する格納を省略しても良い
var vm = new Vue({
  // Vueインスタンスを使用するdivタグを設定.
  el: "#app",

  // イベントハンドラ関数
  method: {
    result() {
      // 何らかの処理
    },
  },
});
(1)

『検索ボタンを押す』というsubmitイベントの発火によって、formタグでイベントに紐付けられているイベントハンドラ関数 (searchメソッド) がコールされる。 (2)

イベントハンドラ関数内のemitメソッドが、親コンポーネントのsearchイベントを発火させる。これに紐付くイベントハンドラ関数 (resultメソッド) がコールされる。

*実装例*

<template>
  <!-- submitイベントが発火すると、紐付くイベントハンドラ関数がコールされる -->
  <form v-on:submit.prevent="search()">
    <!-- submitイベントを発火させるbuttonタグ。submitの場合は省略可能 -->
    <button type="submit">検索する</button>
  </form>
</template>

<script>
  // 変数に対する格納を省略しても良い
  var vm = new Vue({
    // イベントハンドラ関数を定義
    methods: {
      search() {
        // 親コンポーネントのsearchイベントを発火させる。
        this.$emit("search");
      },
    },
  });
</script>

v-on:click="<イベントハンドラ関数>"

View層でクリックイベントが起きた時点で発火し、ViewModel層でバインディングされたイベントハンドラ関数をコールする。

v-on:change="<イベントハンドラ関数>"

View層でinputタグやselectタグで、値の入力後にマウスフォーカスがタグから外れた時点で発火し、ViewModel層でバインディングされたイベントハンドラ関数をコールする

v-on:input="<イベントハンドラ関数>"

View層でinputタグで、一文字でも値が入力された時点で発火し、ViewModel層バインディングされたイベントハンドラ関数をコールする。v-on:changeとは、イベントが発火するタイミングが異なるため、共存できる。


条件付きレンダリング

v-show/v-ifとは

条件分岐を実行するタグであり、v-showまたはv-ifを使用して、プロパティ名を指定する。親テンプレートから渡されたprops内のプロパティ名が持つ値がTRUEの時に表示し、FALSEの時に非表示にする。もし頻繁に表示と非表示の切り替えを実行するようなら、v-ifの方が、描画コストが重たくなるリスクが高くなる為、v-show推奨である。

タグ 使い分け
v-if 単発の切り替えがメインの場合
v-show 表示/非表示の切替回数が多い場合


属性データバインディング

v-bindとは

記入中...


フォーム入力データバインディング

v-modelとは

実装方法は、v-on:input="<イベントハンドラ関数>"と同じである。例えば、以下の2つは同じである。

<input
    type="text"
    v-model="foo">
</input>
<input
    type="text"
    :value="foo"
    @input="eventHandler">
</input>


その他のディレクティブ

v-cloakとは

記入中...


03. コンポーネント

コンポーネントの登録方法

▼ グローバル登録

*実装例*

Vue.component("v-foo-component", {
  template: require("./xxx/xxx/foo"),
});

// 変数に対する格納を省略しても良い
var vm = new Vue({
  el: "#app",
});

▼ ローカル登録

*実装例*

var vFooComponent = {
  // テンプレートと親コンポーネントの対応関係
  template: require("./xxx/xxx/foo"),
};

// 変数に対する格納を省略しても良い
var vm = new Vue({
  el: "#app",

  components: {
    // 親コンポーネントにオブジェクト名をつける。
    "v-foo-component": vFooComponent,
  },
});

ただし、コンポーネントのオブジェクト名の定義は、以下の様に省略できる。

*実装例*

// 変数に対する格納を省略しても良い
var vm = new Vue({
  el: "#app",

  components: {
    // テンプレートと親コンポーネントの対応関係
    "v-foo-component": require("./xxx/xxx/foo"),
  },
});


04. ライフサイクル

ライフサイクルフック

▼ ライフサイクルフックとは

Vueインスタンスの作成から破棄までの間に実行される関数のこと。

全ての関数を使用する必要はない。

順番 フック名 タイミング
1 beforeCreate Vueインスタンスの作成前
2 created Vueインスタンスの作成後
3 beforeMount Vueインスタンスがマウントされる前
4 mounted Vueインスタンスがマウントされた後
5 beforeUpdate データ更新時の再レンダリング前
6 updated データ更新時の再レンダリング後
7 beforeDestroy Vueインスタンスが削除される前 ($destroyメソッド実行前)
8 destroyed Vueインスタンスが削除された後 ($destroyメソッド実行後)

▼ マウントとは

ブラウザ上のリアルDOMの要素を、Vue.jsの処理によって作成される仮想DOMの要素で置き換えること。

▼ beforeCreate

Vueインスタンスの作成前に実行される。

*検証例*

beforeCreateフックの動作を検証する。

dataオプションは、Vueインスタンス作成後に有効になるため、beforeCreateフックでコールできず、undefinedになる。

<template>
  <div>{{ name }}</div>
</template>
<script>
  new Vue({
    data() {
      return {
        foo: "Hiroki",
      };
    },

    beforeCreate() {
      console.log(this.name);
    },
  });
</script>
# 結果
undefined

▼ created

フックの中で特によく使用する。

Vueインスタンスの作成後に実行される。

マウント前に必要な処理を実装する。

*実装例*

非同期通信によるデータを取得、マウント時に必要なデータの準備など

*検証例*

createdフックの動作を検証する。

dataオプションは、Vueインスタンス作成後に設定されるため、createdフックでコールでき、処理結果が表示される。

<template>
  <div>{{ name }}</div>
</template>
<script>
  // 変数に対する格納を省略しても良い
  var vm = new Vue({
    data() {
      return {
        name: "Hiroki",
      };
    },

    created() {
      console.log(this.name);
    },
  });
</script>
# 結果
"Hiroki"

▼ beforeMount

Vueインスタンスがマウントされる前に実行される。

*検証例*

beforeMountフックの動作を検証する。

dataオプションからname変数に対する展開は、マウントによって実行される。

そのため、beforeMountフックの段階では要素自体が作成されておらず、何も表示されない。

<template>
  <div>{{ name }}</div>
</template>
<script>
  // 変数に対する格納を省略しても良い
  var vm = new Vue({
    data() {
      return {
        name: "",
      };
    },

    beforeMount() {
      this.name = "Hiroki";
    },
  });
</script>
# 結果
# 要素が作成されていないため、何も表示されない。

▼ mounted

フックの中で特によく使用する。

Vueインスタンスがマウントされた後に実行される。

要素を操作する処理を実装する。

まず、Vueインスタンスにelオプションが設定されているかを識別し、これに応じて処理の流れが分岐する。

SSRの場合には使用できない。

*実装例*

Vue.js以外の外部パッケージの読み出し、検索実行時のイベントハンドラ関数、ページング実行時のイベントハンドラ関数など

*検証例*

beforeMountフックの動作を検証する。

dataオプションからname変数に対する展開は、elementに対するマウント中に実行される。

そのため、mountedメソッドが実行され、空文字が上書きされる。

<template>
  <div>{{ name }}</div>
</template>
<script>
  // 変数に対する格納を省略しても良い
  var vm = new Vue({
    data() {
      return {
        name: "",
      };
    },

    mounted() {
      this.name = "Hiroki";
    },
  });
</script>
# 結果
"Hiroki"

ただし、全ての子コンポーネントでマウントが完了したことを待つために、nextTickメソッドを使用する必要がある。

<template>
  <div>{{ name }}</div>
</template>
<script>
  // 変数に対する格納を省略しても良い
  var vm = new Vue({
    data() {
      return {
        name: "",
      };
    },

    mounted() {
      this.$nextTick(function () {
        this.name = "Hiroki";
      });
    },
  });
</script>

▼ beforeUpdate

データが更新される時の再レンダリング前に実行される。

再レンダリング前に要素を操作する処理を実装する。

*実装例*

WindowオブジェクトやDocumentオブジェクトのメソッドによる要素の取得など

*検証例*

<template>
  <div>{{ name }}</div>
</template>
<script>
  // 変数に対する格納を省略しても良い
  var vm = new Vue({
    data() {
      return {
        name: "",
      };
    },

    mounted() {
      this.$nextTick(function () {
        this.name = "Hiroki";
      });
    },

    beforeUpdate() {
      console.log(this.name);
    },
  });
</script>
# 結果
"Hiroki"

▼ updated

データが更新される時の再レンダリング後に実行される。

再レンダリング後に要素を操作する処理を実装する。

<template>
  <div>{{ name }}</div>
</template>
<script>
  // 変数に対する格納を省略しても良い
  var vm = new Vue({
    data() {
      return {
        name: "",
      };
    },

    mounted() {
      this.$nextTick(function () {
        this.name = "Hiroki";
      });
    },

    beforeUpdate() {
      console.log(this.name);
    },
  });
</script>
# 結果
"Hiroki"

▼ beforeDestroy

Vueインスタンスが削除される前に実行する。

インスタンスを削除する前に無効化しておく必要のある処理を実装する。

SSRの場合には使用できない。

▼ destroyed

Vueインスタンスが削除された後に実行する。

インスタンスを削除した後のTearDown処理を実装する。

SSRの場合には使用できない。