Help us understand the problem. What is going on with this article?

Vue.jsをMPAで導入してみた

More than 3 years have passed since last update.

mixiグループ Advent Calendar 2016の16日目の記事です。

はじめに

チケットフリマNo1.チケットキャンプを運営する株式会社フンザでは、新サービスを絶賛開発中です。
新サービスをローンチするにあたって、集客の面からSEOを無視できないことや、SPA+サーバサイドレンダリングは敷居高い(サーバサイドがDjangoフレームワークのため)などの理由から、MPAアーキテクチャで構成されています。
その際にフロントエンドのライブラリとしてVue.jsを採用しました。しかしながら、Vue.jsはSPAでの事例に比べ、MPAの例は少なく、苦労することがあったので、その知見をまとめました。

コンポーネントにデータをどう渡すのか?

下記のような方法で実装しました。
1. ページ読み込み時にグローバル変数にJSONデータを代入する
2. その変数をコンポーネントのdataに定義する。
3. コンポーネントのdataから変数をレンダリングする

入力フォームの実装例

form.html
<form>
  <input type="number" name="price" v-model="price" @change="validate">
  <div v-if="error">{{ error }}</div>
</form>

<!-- サーバサイドでHTMLを生成する際に必要なデータをJSONで渡す -->
<script>
var DATA = {
  price: 0,
  error: '金額を入力してください'
};
</script>
form.js
new Vue({
  data: DATA,
  methods: {
    validate () {
      if (!parseInt(this.price, 10)) {
        this.error = '金額を入力してください'
      }
    }
  }
})

ネストしたオブジェクトがバインドされない問題

実際はオブジェクトをネストした状態で渡ることもある
以下の例は、先の例とは異なり、入力してもエラーが表示されません

具体例

form.html
<form>
  <input type="number" name="price" v-model="form.price" @change="validate">
  <div v-if="errors.price">{{ errors.price }}</div>
</form>

<!-- サーバサイドでHTMLを生成する際に必要なデータをJSONで渡す -->
<script>
var DATA = {
  form: {},
  errors: {}
};
</script>
form.js
new Vue({
  data: DATA,
  methods: {
    validate () {
      if (!parseInt(this.form.price, 10)) {
        this.errors.price = '金額を入力してください'
      }
    }
  }
})

解決法

dataに初期値を定義すると動作するようになります。
バインドしたい変数はあらかじめ定義する必要があります。

form.js
new Vue({
  el: 'body',
  data: {
    form: Object.assign({'price': ''}, DATA['form']),
    errors: Object.assign({'price': ''}, DATA['errors'])
  },
  methods: {
    validate () {
      if (!parseInt(this.form.price, 10)) {
        this.error = '金額を入力してください'
      }
    }
  }
})

DOMを操作したい

なるべくDOMを操作したくはないものですが、スマートフォンに対応したWebアプリケーションでは、
必要になることがあるかと思います。
今回は、オーバーレイ表示でその下のスクロールを抑止する方法を紹介します。

overlay.html
<body :class="classes">
  <div v-if="isOpeninigOverlay">
    オーバーレイ表示する
  </div>
</body>
overlay.css
body.scroll-disable {
  height: 100%;
  overflow: hidden;
}
overlay.js
const app = new Vue({
  el: 'body',
  data: {
    classes: [],
    isOpeninigOverlay: false,
    tempScrollTop: 0
  },
  watch: {
    isOpeninigOverlay (willOpen) {
      if (willOpen) {
        // スクロールがリセットされてしまうので、スクロール位置を保持する
        this.tempScrollTop = document.body.scrollTop
        // 下のクラスを適用するとbodyが縮まる
        this.classes.push('scroll-disable')
      } else {
        this.classes.$remove('scroll-disable')
        document.body.scrollTop = this.tempScrollTop
      }
    }
  }
})
app.isOpeninigOverlay = true

ポイントはwatchを使うことで、フラグの値を変更するだけで、DOM操作を行うことができ、かつ必要な処理を一箇所にまとめられることです。

templateを使用したコンポーネントはどういうDOMになるのか?

コンポーネントのDOMツリーが推測できないと、CSSを定義する際に困りますが、
ドキュメントにはそういった記述がないので、試してみました。

マウントポイントとtemplateでclass属性を定義するとどうなる?

dom.html
<div is="custome" id="hoge_id" class="hoge">
  <span>hoge</span>
</div>
dom.js
Vue.component('custom': {
  template: '<div class="fuga"><slot></slot>fuga</div>'
})

結果: DOMの定義がマージされる

DOM
<div is="custome" id="hoge_id" class="hoge fuga">
  <span>hoge</span>fuga
</div>

総括

MPAであってもSPAでのベストプラクティスは活かせる

Ajaxで取得したデータもコンポーネントのdataに格納したり、入力値をバインドしたりと、コンポートネントを単一のストアとして扱うような実装になりました。
結果としては、React+Flexに近い構成になり、最終的にはコードの見通しが良くなりました。

子コンポーネントにデータを渡す場合も、当初はコンポーネントのpropsにJSONで渡していたが、親コンポーネントを経由してpropsで子コンポーネントにデータを渡す実装に変更しました。

Vue.jsはMPAに適している?!

Vue.jsはReact.jsなどのライブラリと比較して、DOMの断片にコンポーネントを適用できる点において、MPAに適している印象です。
jQueryなどのライブラリでも同様のメリットは享受できますが、コードが肥大化していくとメンテナンスが厄介なため、モダンなMPA開発においてはアリだと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした