脱jQueryという主張をよく耳にします。
私の個人プロジェクト「Beautifl - Flash Gallery」のリニューアルでも、依存しまくっていたjQueryの採用をやめました。
サイトを立ち上げたのは8年前の2009年。当時は最盛期だったjQueryをふんだんに使って、インタラクションの充実したRIAの開発に挑戦していました(参照:「wonderflのギャラリーサイトBeautiflを作りました」)。
この記事では、なぜjQueryをやめようと思ったのか、別の技術で得たものは何なのかを紹介します。
▲リニューアルしたBeautiflは、jQueryを全て抜きました
リニューアルにあたってJavaScriptで改善したかったこと
リニューアルにあたって改善したいことがありました。
- 昔のブラウザのためだけの、古いコードが残っている
- モデルとビューの混在を整理したい
- jQueryのままだとWAI-ARIAなどの対応が面倒
「jQueryは時代遅れ…」という思いは個人的にはまったくないものの、実現したいイマドキの機能要件を満たすには物足りなくなってきていました。
新規プロジェクトであれば初めからAngularやReact、Polymer、Vue、RiotなどのJSライブラリを検討できるでしょう。今回はjQueryで完成した土台がある状態で、いかにして脱jQueryとするかが鍵でした。
使いみちのない古いコードとは
「はてなブックマーク」によるとBeautiflは「Flashギャラリーなのに、なぜかHTML5でできてる」とコメントももらっているなど、当時としては先進的にHTML5の導入に挑戦していました。過渡期の時代は華やかに見えて、実は裏側は混沌としたもの。
例として「透過」を紹介します。
当時は満足に使えなかった透過処理
当時のIE7やIE8では透過PNGをまともに使えなかったり、CSSの不透明度プロパティーが使えませんでした(参照「Can I use... - Opacity」)。モダンブラウザでは透過を適用し、IEでは透過処理をスキップするには分岐が必要です。
具体的に実装していたコードは次のようなもの。
if ($.support.opacity === true){
$("#searchResultContainer").css("opacity", 0);
}
jQueryには、ブラウザに特定の機能を持ち合わせているかを調べるヘルパー機能がありました。過渡期においても新旧様々なブラウザに幅広く対応できる手助けになったことは間違いありません。
ただ、透過が扱えないブラウザのための分岐コード$.support.opacity === true
はまったく今のブラウザ環境では意味をなさないですよね。こういった古いコードは根こそぎ削除していきました。
標準APIへの移行
JavaScriptでは8年前と比べるとかなり多くのAPIが充実しています。例えば、document.querySelectorAll()
メソッドなどが代表的なものでしょう。参照したい要素に簡単にたどり着けます。
jQueryはブラウザの違いを吸収でき恩恵が大きかったのですが、今は標準のAPIだけで十分実現できるようになりました。
ロールオーバーの処理
ロールオーバーとロールアウトを実装するコードを例に紹介します。
✏ 昔のコード
var cnt = 0;
$("aside ul.category li a").each(function(){
$(this)
.attr("data-index", cnt)
.hover(
function(){
// ロールオーバー処理
},
function(){
// ロールアウト処理
});
cnt++;
});
こういったコードは次のように改善しました。
✏新しいコード
const listItems = document.querySelectorAll('aside ul.category li a');
for (let i = 0; i < listItems.length; i++) {
const item = listItems.item(i);
item.dataset["index"] = String(i);
item.addEventListener("mouseenter", (event) => {
// ロールオーバー処理
}, false);
item.addEventListener("mouseleave", (event) => {
// ロールアウト処理
}, false);
}
もしかしたらjQueryのほうがコードが短く読みやすいかもしれませんがw
JSONファイル読み込みの処理
AJAXのコードも標準の仕様に置き換えました。
✏ 昔のコード
$.ajax({
url : 'search.php',
data : {
key : query.join(",")
},
dataType : 'json',
success: /* ハンドラーを指定 */
});
✏新しいコード
window.fetch(`search.php?key=${encodeURIComponent(query.join(','))}`)
.then(response => {
return response.json()
})
.then(json => {
/* ハンドラーを指定 */
});
標準のAPIとしてfetch()
で処理できるのはシンプルでいいですね。
アニメーションの指定はJS側ではなくCSS側に
当時はCSS Transitionに対応しているブラウザが限られていたため(参照「Can I use...」)、CSSではなくJSでアニメーションをつけていました。そういった事情もあり、JavaScriptのコードにはモデルとコントロール、ビューが入り混じっています。
例えば、検索バーのフォーカス時のアニメーションの実装をみてみましょう。
✏昔のコード
$('#incrementalSearch')
.focus(focusHandler);
function focusHandler(){
// 状態を保存
this.isSearchFocus = true;
// レイアウトの調整
$("#searchBody").stop().animate({width: 150}, 500, "easeInOutExpo");
}
✏新しいコード
新しい設計では、JSではclass
属性の追加/削除のみを扱い、動きはCSSで実装することに。JS側に記載していたアニメーションの処理はCSSに分離でき、コードの見通しがよくなりました。
template : `
<div class="searchBody" v-bind:class="{focus: isSearchFocus}">
<input v-on:focus="focusHandler" />
</div>`
methods : {
focusHandler: function (event) {
// 状態を保存
this.$data.isSearchFocus = true;
},
}
.searchBody {
width: 100px;
transition: all 0.3s ease;
}
.searchBody.focus {
width: 150px;
}
モデルとビューの分離のためのフレームワークの選定
ゼロスクラッチなSPA(シングルページアプリケーション)だったらAngularやReactも有力な検討候補でしょう。Angularは昨年のカンファレンスng-japanで発表したように、強力で生産性の高いフレームワークです(参照「クリエイティブの視点から探るAngular 2の可能性」)。高機能なSPAをものすごいスピードで開発できます。手前味噌ですが、Chrome Experimentsに選出された「Particle Develop」は、Angular 2を使って実質1週間で開発しました。
また、フレームワークを選定するときは、比較のために試したり、定量的に検証し比較するのがセオリーです。私は普段から様々なフレームワークを検証するのが好きなのですが、AngularやReact、Vueの性能比較を行ったりもしています。
※JSフレームワークのパフォーマンス測定では、Angular(約40fps)>React(約38fps)>>>>Vue.js(約20fps)という結果になりました
今回のBeautifl.netのリニューアルでは結果として、Vue.js 2.2を採用することになりました。その理由は・・・
ゼロスクラッチでないプロジェクトにVue.jsが向いていた
このサイトはPHPでHTMLを出力する構成です。なので、厳密にいうとSPA=シングルページアプリケーションではなく、マルチページアプリケーション(そんな表現あるのかな?)です。サーバー側で出力したHTMLに対して、JS側で処理を乗っけるイメージ。
「既にHTMLがある状態で何かをする」という目的ではVue.jsが適していました。Angular(2以上)やReactだとDOMを一から組み立てていく方法になるため、今回のケースではあまり適しませんでした。
※Angular 1.xもVue.jsと同じようにHTMLに対して制御できますが、「(Angular 2のスキルがある状態で)イマサラAngular 1.xを学ぶのもなぁ」という気持ちもあり、Vue.jsを選択しました。
Vue.jsによってシンプルになったJavaScript
Vue.jsの導入によってシンプルになった制御コードをみていきましょう。サイトのヘッダーにある、インクリメンタルサーチの実装コードです。
✏ 昔のコード
/** 検索結果を受け取ったら */
search_successHandler: function (data, status) {
for (var i = 0; i < data.length; i++) {
str += '<a id="searchItem_' + i + '" href="http://beautifl.net/?id=' + data[i].id + '">';
str += ' <div class="searchItem clearfix">';
str += ' <div class="thumb">';
str += ' <img src="archives_s/' + data[i].wid + '.jpg" width="50" height="50" />';
str += ' </div>';
str += ' <div class="meta">';
str += ' <span class="searchTitle">' + data[i].title + '</span><br />';
str += ' <span class="searchUser">' + data[i].user + '</span>';
str += ' </div>';
str += ' </div>';
str += '</a>';
}
$("#searchResultContainer").html(str);
}
✏新しいコード
<ul v-if="items.length >= 0">
<li v-for="(item, index) in items">
<a v-bind:href="'http://beautifl.net/?id=' + item.id"
v-bind:class="{selected : searchItemSelectedIndex === index}"
aria-selected="searchItemSelectedIndex === index ? 'true' : 'false'">
<div class="searchItem">
<div cass="thumb">
<img v-bind:src="'archives_s/' + item.wid + '.jpg'" width="50" height="50"/>
</div>
<div class="meta">
<span class="searchTitle">{{ item.title }}</span><br/>
<span class="searchUser">{{ item.user }}</span>
</div>
</div>
</a>
</li>
</ul>
/** 検索結果を受け取ったら */
search_successHandler: function (data, status) {
// データをセットする
this.$data.items = items;
}
昔のコードはHTMLを組み立てるのに文字列を結合して作るというストイックなやり方でした。新しいコードでは、HTMLテンプレート側にビューの制御を書き、JS側にロジックを入れています。ビューとモデルを分離できたという点で、見通しやすくなりました。
▲キーボードの↑キーや↓キーの制御も、Vue.js側でプロパティーの数値を変えているだけで実現している
全体的にVue.js 2.2を導入したことで、JavaScriptにはロジックのみを記述することができ(表示制御のコードを分離でき)、可読性が向上しました。
Vue.jsで構築しているのでデベロッパーツールでコンポーネントの状態を分析できます。データ駆動になっていることがお分かりいただけるでしょう。https://t.co/9w5qanumLh pic.twitter.com/B8zpF8Al2D
— 池田 泰延 (@clockmaker) 2017年4月14日
アクセシビリティの向上に
WAI-ARIAはRIAのアクセシビリティのためのAPIです。HTMLのDOM要素にaria-selected
やaria-checked
などの属性を付けることで、ブラウザに要素がどのような状態であるか伝えます。そのことによって、音声リーダーなどで認識させることができるというものです。
参照
WAI-ARIAはHTML要素の属性を動的に書き換えなければならないため、生JSやjQueryで制御しようとすると、正直作業的に骨が折れます。Twitterでアンケートをとってみたところ、WAI-ARIAはあまり採用されていないようですね…。
HTMLコーダーに質問ですが、WAI-ARIA対応していますか?
— 池田 泰延 (@clockmaker) 2016年10月20日
Vue.jsだとHTMLの属性に記載すれば動的に値を書き換えられるので、最小限のコードでWAI-ARIAを設定できます。JS側にあるのは状態を示すプロパティーのみで、JSのロジックにはWAI-ARIAの一切の処理が入っていません。
<button v-bind:aria-checked="checkRollover === true ? 'true' : 'false'"
v-on:click="handleRollOverSelectChange()"
aria-label="Select preview enabled.">
<!-- ROLL OVERのアイコン画像 -->
</button>
モデルとビューを管理する系のJSライブラリはWAI-ARIAのaria-*
属性の更新は適していると思います。Vue.jsに限らずAngularでもReactユーザーの方は積極的にWAI-ARIAを採用したらいいのになぁと思います。
WAI-ARIA対応するには、DOM要素のaria-selectedやaria-checked等の属性を動的に変更する必要があります。
— 池田 泰延 (@clockmaker) 2017年4月14日
「JSでチマチマ書き換えるのは面倒!」と思うかもしれませんが、Vue.jsだとちょっとHTMLに書き足すだけで簡単に対応できます。 pic.twitter.com/FyhPDtyPbW
まとめ
今回の記事はjQueryで解決できなくなった問題に対処するために、Vue.jsを導入したという話でした。記事「CSS Grid Layoutをガッツリ使った所感」もあわせてご覧ください。