jQuery から Vue.js への移行

  • 398
    Like
  • 0
    Comment

はじめに

最近耳にする Vue.js(ビュージェイエス) ってどんなもの? jQuery とどう書き方違うの?とか、jQuery でやってたこういう事って Vue.js だとどうやるの?という jQuery から Vue.js へ移行しようかなぁ~と思っている人向けの小難しいことは省いた記事です。私もそちら側から来たものです。

Vue.js 日本公式ページ(日本語翻訳率が半端ないと評判)

jQuery と Vue.js の違い

jQuery はセレクタ操作に特化したライブラリなので HTML の一部をちょっとだけ弄るには簡単で手軽に扱えます。複雑な処理をしたい場合その都度セレクタから要素を探して操作するので、反映が遅かったり同じデータを表示するはずの複数の要素がちゃんと同期していなかったり管理が大変になってきます。そこまで複雑な事はしていない、現状つらいと感じてなければ無理に変える必要はないと思います。

Vue.js は高速で動く 仮想DOM というものを使った設計で作られており・・・(難しいので省略)簡単にいうと、JavaScript のデータと HTML の要素を紐付けて、データが変われば勝手に表示に反映してくれます。毎回データと表示を同期させる処理を自分で書かなくてもよくなり、メンテナンスする時もデザインとスクリプトを行ったり来たりする事が減ります。インタラクティブなページや、状態管理が活躍するページに向いています。

SEO 的にどうなの?

Vue.js で持っているデータはブラウザのソースコードを見ても表示されません。でも最近のクローラーは Vue.js などの JavaScript で出力された部分も拾ってくれるので気にする程ではないと思います。

シェア用の OGP など考慮する場合は SSR (サーバーサイドレンダリング)というものもあり、最初のアクセスのみサーバー側で初期画面に必要な DOM が展開された状態の HTML を構築し、それ以降は 仮想DOM を通して処理するようになります。

書き方の違い

文字を入れ替える

:mushroom: デモページ

まずは簡単に World を別の文字に変えるコードを作ってみます。

jQuery の場合

HTML
<div>
  Hello <span id="message">World</span> !
  <button id="update">change</button>
</div>
JavaScript
$(document).on('click', '#update', function() {
  $('#message').text('jQuery')
})

jQuery で書くとだいたいこんな感じでしょう。変える部分はタグで囲む必要がありますね。

Vue.js の場合

HTML
<div id="app">
  Hello {{ message }} !
  <button @click="update">change</button>
</div>
JavaScript
new Vue({
  el: '#app',
  data: {
    message: 'World'
  },
  methods: {
    update() {
      this.message = 'Vue.js'
    }
  }
})

Vue.js で書くとこんな感じになります。まだ違いは無いというかむしろ jQuery の方が楽そう。

文字列は最初からデータで持っておき、データの文字列を画面に表示させる場合は {{ message }} と書きます。

@click="式やメソッド" は jQuery の $(element).on('click', ...) と似たような振る舞いをします。メソッド名を書くことも出来るし使いまわさない短いものなら直接式を書いてもいい。

this.message = 'Vue.js'

データを変更すれば一緒に {{ message }} この部分が変更されます。

リスト要素の追加と削除

:mushroom: デモページ

add ボタンを押すとリストに新しい要素を追加し、 remove ボタンを押すとその要素を削除します。ついでに現在のリスト要素の数も表示してみましょう。

jQuery の場合

Ajax で取得したという想定で jQuery も最初はデータから表示するようにしました。

HTML
<div>
  <p>Length: <span id="length">0</span></p>
  <ul id="list"></ul>
  <button id="add">add</button>
</div>
JavaScript
(function() {
  var counter = 0
  var list = ['Apple', 'Banana', 'Strawberry']

  init()

  $(document).on('click', '#add', function() {
    addItem('Orange' + (++counter).toString())
  })
  $(document).on('click', '.remove', function(event) {
    $(event.target).parent().remove()
    updateLength()
  })

  function init() {
    for (var i = 0; i < list.length; i++) {
      addItem(list[i])
    }
  }

  function addItem(name) {
    $('#list').append('<li>' + name + ' <button class="remove">remove</button></li>')
    updateLength()
  }

  function updateLength() {
    $('#length').text($('#list li').length)
  }
})()

jQuery を使うにしてもクラス化したりもっと綺麗に書く人もいると思うけどこんな感じだと思います。

まあ <span id="length">0</span> こいつがよくいる厄介なデータである。 これをリスト要素の数と同期させるには変更があったら毎回 updateLength() をしないといけない。凄く面倒くさい。

テンプレートもロジックに入り込んでいるのでメンテナンスが大変なやつです。

$('#list').append('<li>' + name + ' <button class="remove">remove</button></li>')

あと jQuery はセレクタがないと生きていけないタイプなので操作する場所が増えるに連れて ID やクラスなどの Attribute もどんどん増えていきます。

Vue.js の場合

HTML
<div id="app">
  <p>Length: {{ length }}</p>
  <ul>
    <li v-for="(item, i) in list" :key="item">{{ item }}
      <button @click="list.splice(i, 1)">remove</button>
    </li>
  </ul>
  <button @click="addItem">add</button>
</div>
JavaScript
new Vue({
  el: '#app',
  data: {
    counter: 0,
    list: ['Apple', 'Banana', 'Strawberry']
  },
  computed: {
    length: function() {
      return this.list.length
    }
  },
  methods: {
    addItem: function() {
      this.list.push('Orange' + (++this.counter).toString())
    }
  }
})

だいぶ違いが見えてきました。

addItem は jQuery も Vue.js も新しい要素を追加する関数ですがやっている事が大分違います。要素への追加と削除もデータ側の配列を操作するだけでOK。

this.list.push('Orange' + (++this.counter).toString())

厄介な length はというと、computed という関数型のデータで配列の長さを返すものを1個作っておけばそれがリアルタイムで表示されます。

computed: {
  // この部分がデータと同じ役割
  length: function() {
    return this.list.length
  }
},

勿論 {{ list.length }} と書いても大丈夫だけど、computedキャッシュされるのでフィルターとかの複雑な処理に便利です。

ステキですね。

表示・非表示の切り替えとトランジション/アニメーション

:mushroom: デモページ

よくあるタブ切り替えのコンテンツを作ってみましょう。

button を使えばクリックとエンターが受け取れるのだけどキーコードの確認も結構違うのであえて tabindex を使いました。 WAI-ARIA とかまでは考慮してません。

jQuery の場合

HTML
<div>
  <ul id="tab" class="tab">
    <li data-id="1" tabindex="0">menu1</li>
    <li data-id="2" tabindex="0">menu2</li>
    <li data-id="3" tabindex="0">menu3</li>
  </ul>
  <div id="content" class="content">
    <section data-id="1">
      <p>content1</p>
    </section>
    <section data-id="2">
      <p>content2</p>
    </section>
    <section data-id="3">
      <p>content3</p>
    </section>
  </div>
</div>
JavaScript
(function() {
  var current = 1
  init()

  $('#tab').on('click', 'li', function(event) {
    changeTab($(this).data('id'))
  })
  $("#tab").on('keypress', 'li', function(event) {
    if (event.keyCode == 13) {
      changeTab($(this).data('id'))
    }
  })

  function init() {
    changeTab(current)
  }

  function changeTab(id) {
    $('#content section:not([data-id="' + id + '"])').removeClass('active')
    $('#content section[data-id="' + id + '"]').addClass('active')
    $('#tab li:not([data-id="' + id + '"])').removeClass('active')
    $('#tab li[data-id="' + id + '"]').addClass('active')
  }
})()

アクティブな要素にクラスを付けたり消したりします。

display:none が付いてるので切り替えのフェードインにはトランジションの代用でアニメーションを使用しました。 visibility などを使ってもいいけど、このへんの実装も CSS 大好きじゃないとだれてきます。

私もフェードアウト作る時点でだれてしまいました。

Vue.js の場合

HTML
<div id="app">
  <ul class="tab">
    <li v-for="item in list" 
        @click="changeTab(item.id)" 
        @keyup.enter="changeTab(item.id)" 
        :class="{active:active(item.id)}" tabindex="0">{{ item.label }}</li>
  </ul>
  <div class="content-vue">
    <transition>
      <section v-for="item in list" :key="item.id" v-if="active(item.id)">
        <p>{{ item.content }}</p>
      </section>
    </transition>
  </div>
</div>
JavaScript
new Vue({
  el: '#app',
  data: {
    current: 1,
    list: [{
      id: 1,
      label: 'menu1',
      content: 'content1'
    }, {
      id: 2,
      label: 'menu2',
      content: 'content2'
    }, {
      id: 3,
      label: 'menu3',
      content: 'content3'
    }]
  },
  methods: {
    active: function(id) {
      return this.current == id
    },
    changeTab: function(id) {
      this.current = id
    }
  }
})

Vue.js はテンプレートにも v-if という条件分岐が書けるのでアクティブな ID のコンテンツだけを表示させます。

<section v-for="item in list" :key="item.id" v-if="active(item.id)">
  <p>{{ item.content }}</p>
</section>

そして切り替えトランジションは <transition> で囲むだけで Vue が時間を管理し切り替えが始まったな~というタイミングでクラスが付き、終わったな~というタイミングで display:none や要素が削除されるので、あとは提供されたクラスにスタイルをつければいい。

CSS
.v-enter-active, .v-leave-active {
  transition: all .8s;
}

.v-leave-active {
  position: absolute;
}

/* 表示されるときは左から */
.v-enter {
  transform: translateX(-20px);
  opacity: 0;
}

/* 消えるときは上へ */
.v-leave-to {
  transform: translateY(-20px);
  opacity: 0;
}

複雑なトランジションが作れるし、もう display:none に悩まされる事はないですね。

リスト要素の場合でも transition-group というタグに変えると同じように動きを付けることが出来ます。公式にいくつかサンプルがあるのだけど、オリジナルの動きも CSS 部分を変えるだけで簡単に作れるのでかなり面白いです。

:mushroom: 公式ガイド リスト移動トランジション

ちなみに @click="..." とか v-for とかごちゃごちゃ書かれてますがテンプレートはコンパイルされるので出力される DOM には入りません。

まとめ

作るページによって使い分けるのもいいと思います。個人的にはトランジションが楽なので何か動きが入れば割りと気軽に Vue を使ってます。

Vue.js と似たようなデータバインディング系のライブラリは他にもいろいろあるので、自分にあったライブラリを使うといいです。個人的には Vue.js は入門のハードルも低いし、追々大きなシステムにも対応出来るし、一番大事なとこで使ってて凄く楽しいのでオススメです。

Vue.js は 公式マニュアルの日本語翻訳率とスピードが早いので、まずは公式マニュアルを読むのが一番正確だし間違いないです。それにプラスして色んな方が書いた Tips も沢山あるので探してみてください。私も書いているのでぜひ見てくださいね~(•ө•)ノ

:mushroom: Vue.js 日本公式ガイド
:mushroom: 私のブログ #Vue.js