jQuery から Vue.js へのステップアップ


はじめに

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

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

もともと「jQuery から Vue.js への移行」というタイトルでしたが、それだと jQuery を完全にやめる、的なニュアンスになってしまうので、少し変更しました🐹


jQuery と Vue.js の違い

jQuery は、セレクタ操作に特化したライブラリで HTML の一部をちょっとだけ弄るには手軽に扱えます。更新のたびにセレクタから要素を探して操作をするため、複数の UI を連携させるようなページでは、同じデータを表示するはずの複数の要素がちゃんと同期していなかったり、全体の管理が大変になってきます。シンプルなページにとっては低コストで導入できる&理解者の多い便利なライブラリなので「そんな複雑な事はしていない」と現状つらい状況でなければ無理に変える必要はないのではないかと思います。

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


SEO 的にどうなの?

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

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


書き方の違い

jQuery も Vue.js も UI を構築するためのライブラリなので、実際に作るものは似通っています。

簡単な例を使って書き方の違いを紹介します!


文字を入れ替える

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

See the Pen jzwZRv by mio (@mio3io) on CodePen.


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 }} この部分が変更されます。


リスト要素の追加と削除

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

See the Pen qojoBZ by mio (@mio3io) on CodePen.


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']

$(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)
}

init()
})()


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

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

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

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

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


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キャッシュされるのでフィルターとかの複雑な処理に便利です。

ステキですね。


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

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

See the Pen xWrWbw by mio (@mio3io) on CodePen.

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

$('#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')
}

init()
})()


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

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> で囲むだけで OK。Vue が時間を管理し切り替えが始まったな~というタイミングでクラスが付き、終わったな~というタイミングで display:none や要素が削除されるので、あとは提供されたクラスにスタイルを定義するだけで OK!


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.js を使っています。

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

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

:mushroom: Vue.js 日本公式ガイド

:mushroom: 私のブログ #Vue.js

PS:多分2018の初夏頃にVueの本を出します。どうぞよろしくお願いいたします!

→ 出ました! 基礎から学ぶ Vue.js 猫ちゃんが目印🐾