フロントエンド開発Blog

オレには鈍器がある

このエントリーをはてなブックマークに追加

CSS Modules , ES2015 , JavaScript , Vue.js , Vuex , babel , webpack

前回はwebpackとかファイル構造とかStoreについて書きました。今回からは3回に分けてcomponents周りを解説していきたいと思います。まずはcomponentsの構成から。

components構成

routerから直接呼ばれる上位コンポーネントと、それにぶら下がる子コンポーネントに大別できます。構成は以下の通りです。


Monthly.vue
 KkbDataCalc
 KkbDataChart
  KkbDataChartFuufu
  KkbDataChartCategory
 KkbDataTable
 Notification
 Loading

Edit.vue
 KkbForm
 Notification
 Loading

Yearly.vue
 KkbYearlyChart
  KkbYearlyChartCategory
  KkbYearlyChartFuufu
 KkbDataCalc
 KkbDataChart
  KkbDataChartFuufu
  KkbDataChartCategory
 KkbDataTable
 Notification
 Loading

Monthly.vue、Edit.vue、Yearly.vueの3種類に大別でき、それぞれに子コンポーネントがぶら下がっているようなイメージです。Monthly.vueから個々に見ていきます。


Monthly.vue以下のコンポーネント群


Monthly.vue
 KkbDataCalc
 KkbDataChart
  KkbDataChartFuufu
  KkbDataChartCategory
 KkbDataTable
 Notification
 Loading

templateの冒頭にdiv.monthlySelectという前月来月へ移動するボタンを持つ要素があります。それぞれprevYearMonth、nextYearMonthという変数を用いてリンクが生成されています。これらはcomputedによる演算整形後のデータを使用しています。Store.state.monthly.currentYearMonthの値を基準に前後月を計算します。このStore.state.monthlyは子コンポーネントKkbDataTableからactionsを発行し、AJAXで取得したデータをmutations経由で更新されるデータになります(後述)

KkbDataCalc

Store.state.monthlyデータから、トータル支出・旦那支出・嫁支出の表示と差額表示を行っています。

Store.state.monthlyデータをこのコンポーネントで使用できるように、VuexのmapState関数を使用します。


import { mapState } from 'vuex'

//~~途中割愛~~
  computed: {
    // VuexのStateを展開
    ...mapState({
      monthly: 'monthly'
    }),

これでStore.state.monthlyの値がmonthlyという命名で使用できるようになりました。

このコンポーネントの前半部分(div.sumStatus要素。月の支出・差額表示部分)はかなりシンプルです。stateの内容をmain.jsで定義したfilterで整形して表示したり、computedを使って差額計算をして表示したりしてます。スタンダードで分かりやすいVue.jsの使い方で特に難しくないと思います。

後半のカテゴリー別の支出表示(div.categoryStatus以下の部分)にはv-forを使用しています。配列を受け取り、繰り返し表示ができます。ここでは以下のようなlistデータを繰り返し処理しています。


"list": [
        {
            "category_id": "1",
            "category_name": "食料品",
            "sum_danna": "1000",
            "sum_price": "1200",
            "sum_yome": "200"
        },
        {
            "category_id": "2",
            "category_name": "雑貨",
            "sum_danna": "1000",
            "sum_price": "2000",
            "sum_yome": "1000"
        },
        {
            "category_id": "3",
            "category_name": "生活消耗品",
            "sum_danna": "1000",
            "sum_price": "1000",
            "sum_yome": null
        },

また、CSSModulesの使い方もここで覚えておくと良さそうです。CSSをインポートし、template上で:classとしてバインドします。

script

import Style from '../style/common.css'

export default {
  name: 'kkb-data-calc',
  data: function(){
    return {
      style: Style,
    }
  },
template

{{payPrice}}円

class名にハイフンが使われている場合は以下のように記述します。そうしないとマイナス記号として認識され、エラーになります。


<span :class="sprite01['icon-left']"></span>

KkbDataChart

KkbDataChartFuufuKkbDataChartCategoryStore.state.monthlyをpropsとして渡しているだけです。

KkbDataChartFuufuKkbDataChartCategory

vue-chartjsの記法に則って記述しています。わざわざStore.state.monthlyをpropsとして渡したのは以下の記述のためです。


watch: {
    'chartState' () {
      this.fillData()
    }
  },

watchとは、値の変更を監視して何らかの変更を検知したら処理を実行するオプションになります。ここで親から渡されたpropsを指定し、変更を検知したらグラフを再描画しています。

ここを直接Store.state.monthlyをwatchするようにしたところ上手く動作しなかったためprops経由で渡されたデータを監視するようにしました。(やり方が悪いだけかもしれませんが・・・)

KkbDataTable

template自体はシンプルですが、このコンポーネント内でactionsを発行している箇所があるため侮れません。

VuexのmapActionsという関数を使うことで、このコンポーネントからactionsが叩けるようになります。


  methods: {
    // Vuexのactionsを展開
    ...mapActions ({
      getMonthlyData: 'getMonthlyData',
      delEntry: 'delEntry',
    }),

発行したいアクションは月別データを取得するgetMonthlyDataと、登録データを削除するdelEntryの二つです。

getMonthlyDataこのコンポーネントがマウントされたときとrouterのparamsに変更があったときに発火させています。


  /**
   * マウント時に毎回実行
   */
  mounted: function(){
    // 当コンポーネントがマウントされたらAJAXでデータ同期
    let yearmonth = this.getYearmonth(this.$route.params.yearmonth);
    this.getMonthlyData(yearmonth);
  },
  // routerの変更を監視
  watch: {
    '$route' (to, from) {
      // 当コンポーネントがマウントされた状態でrouterのパラメータが変更されたらデータ同期
      let yearmonth = this.getYearmonth(to.params.yearmonth);
      this.getMonthlyData(yearmonth);
    }
  }

delEntryは@clickイベントで発行します。削除ボタンに直接actions関数を指定しています。


<button @click="delEntry({d, idx, category})" :class="style.btnB">削除</button>

actionsにオプション値を送っています。./store/actions.jsを見ると分かりますが、オブジェクトとして受け取っています。


// ./store/actions.js 70行目付近

/**
 * 該当データ削除ボタンの挙動
 * @param  {Object}     Vuexオブジェクト
 * @param  {Object}     option連想配列(d: 現在の月別データ(store.state.monthly.detailと同じ形式, idx: 削除対象の配列番号, category: 支払いカテゴリーリスト)
 */
export const delEntry = function({commit}, {d,idx,category}){
  let _this = this;
  let data = d.Form;

また、AJAX全般そうですが、actionsの中で本番用Buildと開発用BuildでAJAXオプションの指定を変えている箇所があります。本番では認証情報をぶら下げてPOSTとして送信しますが、開発時は静的なstubをjsonファイルとして用意してGETするようにします。以下のようにifで分岐します。


  // AJAXオプション設定
  let ajaxUrlString = ajaxUrl.delete;
  let ajaxOption    = {};

  // AJAXオプションをproductionとdevで分ける
  if(process.env.NODE_ENV === 'production'){
    let method = "POST";
    //let body = JSON.stringify({start: yearmonth})
    // FormDataオブジェクト形式でPOSTデータを生成
    var body = new FormData();
    body.append("id", data.id);

    let headers = {'Accept': 'application/json'}

    ajaxOption = {
      method,
      headers,
      body,
      credentials: 'include'
    }
  }else{
    ajaxUrlString = ajaxUrlString + "?id=" + data.id;
  }

// ~略~
  fetch(ajaxUrlString, ajaxOption)

これらactionsの最後にcommit(types.UPDATE_MONTHLY_DATA, json)などのようにmutationsにjsonデータを引き渡しています。

jsonを受け取ったmutationsはstateを更新する流れになります。

NotificationLoading

ほかの上位コンポーネントでも読み込んでいます。どちらもシンプルで、Store.state.notificationStore.state.isLoadingの値に応じて表示非表示を切り替えているだけです。

actionsのAJAX実行シーケンス内で使用されます。


  // ./store/actions.js の一例

  // loadingアニメーション再生 //★ココ
  commit(types.UPDATE_LOADING, true);

  fetch(ajaxUrlString, ajaxOption)
    .then(function(res){
      // loadingアニメーション停止 //★ココ
      commit(types.UPDATE_LOADING, false);

      if(res.ok){
        return res.json();
      }else{
        // error //★ココ
        commit(types.UPDATE_NOTIFICATION, "(network error)月別データ取得に失敗しました。\nインターネットに接続しているか確認してください。");

Notificationはトーストメッセージを一定時間表示したあと、自動で閉じるようにするためにmutations上でタイマーを仕込んであります。以下./store/mutations.jsの該当の記載です。


// ./store/mutations.js 70行目付近

  /**
   * 注記メッセージを更新
   * @param  {Object} state state
   * @param  {String} msg   表示メッセージ文字列
   */
  [types.UPDATE_NOTIFICATION]: function(state, msg){
    state.notification.msg = msg;

    if( timerId != null ){
      clearTimeout(timerId);
      timerId = null;
    }

    // 指定秒数表示した後に消す //★ココ
    timerId = setTimeout(function(){
      timerId = null;
      state.notification.msg = "";
    }, 1500);
  },

今回はcomponentsの構成とMonthly.vueに関する解説でした。次回に続きます。

ページトップへ

関連ページ

ページトップへ