2016-11-17

まだjQueryで消耗してるの? これからはVue.jsでラクにいこう

近年、Web業界の発展により、UI/UXの優れたWebサイトやWebアプリケーションが登場してきています。

そして、それに合わせ、フロントエンドの技術が進歩し、UIを構築するのに優れたライブラリやフレームワークが多く登場してきています。

周りがどんどん進化しているのに、まだjQueryで頑張っちゃってるなんてこと、まさかないですよね? もしもまだあなたが、JavaScriptにおいてjQueryしか使ったことがないのであれば、jQueryで消耗してるとしか言いようがありません。

Vue.js(JavaScriptフレームワーク)とは

vue

私はこれまで、AngularJS(Angular)やReact.js、knockout.jsなどといったJavaScriptフレームワークを利用してきましたが、中でもVue.jsは、とにかく全ての人たちにオススメできるJavaScriptフレームワークであり、学習コスト的にもjQueryを淘汰できる唯一の存在と考えています。

JavaScriptフレームワークとは、データを表示したり操作したりする、JavaScript上の"枠組み"と捉えてもらえばいいでしょう。

jQueryは、「JavaScriptフレームワーク」ではありません。ただ単に、JavaScriptの記述を簡素化できるというだけのライブラリです。

つまり、jQueryを使う上において、データをどのように表示したり操作したりするのか、その方法は定義されていないのです。なので、開発者それぞれが自由気ままに実装してしまい、可読性も拡張性も安定性も何もないUIが出来上がりやすいのです。

Vue.jsのようなフレームワークの採用が、出来上がるUIの完成度を大きく左右していく要因であることは、明白です。

Vue.jsとjQueryの比較

では実際に、jQueryで記述したコードが、Vue.jsではどのようになるのか、提示していきます。

ここでは、Google検索のように、テキストボックスに英語を入力すると、その入力された文字から「国名(英語)」を予測して表示するUIを実装してみることにします。よく見るUIの一つですよね。

ではまずjQueryのコードから。

<html>
  <head>
    <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"
            integrity="sha256-/SIrNqv8h6QGKDuNoLGA4iret+kyesCkHGzVUUV0shc="
            crossorigin="anonymous"></script>
    <style>
      .country-list-box {
        position: absolute;
        border: 1px solid #eee;
      }
      .country-list {
        padding-right: 1rem;
      }
      .important {
        color: red;
      }
    </style>
  </head>
  <body>
    <main>
      <input id="input" type="text">
      <div class="country-list-box">
        <ul id="countries" class="country-list"></ul>
      </div>
    </main>
    <script>
      jQuery(function ($) {
        var countries = ["Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda", "Argentina", "Armenia", "Australia", "Austria", "Azerbaijan", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Brazil", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Canada", "Cape Verde", "Central African Republic", "Chad", "Chile", "China", "Colombia", "Comoros", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "FYR Macedonia", "Fiji", "Finland", "France", "Gabon", "Georgia", "Germany", "Ghana", "Greece", "Grenada", "Guatemala", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Jamaica", "Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Kosovo", "Kuwait", "Kyrgyz Republic", "Lao P.D.R.", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Mauritania", "Mauritius", "Mexico", "Micronesia", "Moldova", "Monaco", "Mongolia", "Montenegro", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue", "North Korea", "Norway", "Oman", "Pakistan", "Palau", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", "Portugal", "Qatar", "Republic of Congo", "Romania", "Russia", "Rwanda", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovak Republic", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Korea", "South Sudan", "Spain", "Sri Lanka", "St. Kitts and Nevis", "St. Lucia", "St. Vincent and the Grenadines", "Sudan", "Suriname", "Swaziland", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "The Bahamas", "The Gambia", "Timor-Leste", "Togo", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States", "Uruguay", "Uzbekistan", "Vanuatu", "Vatican", "Venezuela", "Vietnam", "Yemen", "Zambia", "Zimbabwe"];
        var $countries = $('#countries');
        $countries.parent().hide();
        $('#input').on('input', function () {
          var text = $(this).val().toLowerCase();
          $countries.empty().parent().hide();
          if (!text) return;
          countries.forEach(function (country) {
            var index = country.toLowerCase().indexOf(text);
            if (index !== -1) {
              var html = '<li>';
              html += country.slice(0, index);
              html += '<span class="important">';
              html += country.slice(index, index + text.length);
              html += '</span>';
              html += country.slice(index + text.length);
              html += '</li>';
              $countries.append(html);
            }
          });
          if (!$countries.is(':empty')) {
            $countries.parent().show();
          }
        });
      });
    </script>
  </body>
</html>

上記のデモはこちらから。

jQueryでも、問題なく普通に書くことはできます。

しかし、jQueryでは、表示されているデータの整合性や、HTML構造自体の整合性を、自分で管理する必要があり、実際にコード上でそれを意識しています。例えば、<ul>の中には<li>しか記述できませんが、それをJavaScriptコードを記述する時点で意識する必要があり、責任の分担ができていません。

また、例ではjQueryオブジェクトのparent()メソッドを利用してみましたが、HTMLの構造が変更されると、この部分が動かなくなる可能性があります。他の部分も、いまは大丈夫でも、今後コード量が莫大になったときに大丈夫か、ということです。

parent()メソッドなどを利用しない方針にするにしても、次は莫大な量のclassを管理する必要が出てくるなど、jQueryを使う上においては、工夫をどれだけやっても、なかなか大規模にはしにくく、拡張に弱いのです。

次に、Vue.jsのコード。

<html>
  <head>
    <script src="https://unpkg.com/vue@2.0.7/dist/vue.min.js"></script>
    <style>
      .country-list-box {
        position: absolute;
        border: 1px solid #eee;
      }
      .country-list {
        padding-right: 1rem;
      }
      .important {
        color: red;
      }
    </style>
  </head>
  <body>
    <main id="app">
      <input type="text" v-model="text">
      <div v-show="countryListObjects.length" class="country-list-box">
        <ul class="country-list">
          <li v-for="obj in countryListObjects">{{obj.before}}<span class="important">{{obj.text}}</span>{{obj.after}}</li>
        </ul>
      </div>
    </main>
    <script>
      (function(Vue){
        var app = new Vue({
          el: '#app',
          data: {
            countries: ["Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda", "Argentina", "Armenia", "Australia", "Austria", "Azerbaijan", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Brazil", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Canada", "Cape Verde", "Central African Republic", "Chad", "Chile", "China", "Colombia", "Comoros", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "FYR Macedonia", "Fiji", "Finland", "France", "Gabon", "Georgia", "Germany", "Ghana", "Greece", "Grenada", "Guatemala", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Jamaica", "Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Kosovo", "Kuwait", "Kyrgyz Republic", "Lao P.D.R.", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Mauritania", "Mauritius", "Mexico", "Micronesia", "Moldova", "Monaco", "Mongolia", "Montenegro", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue", "North Korea", "Norway", "Oman", "Pakistan", "Palau", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", "Portugal", "Qatar", "Republic of Congo", "Romania", "Russia", "Rwanda", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovak Republic", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Korea", "South Sudan", "Spain", "Sri Lanka", "St. Kitts and Nevis", "St. Lucia", "St. Vincent and the Grenadines", "Sudan", "Suriname", "Swaziland", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "The Bahamas", "The Gambia", "Timor-Leste", "Togo", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States", "Uruguay", "Uzbekistan", "Vanuatu", "Vatican", "Venezuela", "Vietnam", "Yemen", "Zambia", "Zimbabwe"],
            text: ''
          },
          computed: {
            countryListObjects: function () {
              var obj = [];
              var text = this.text.toLowerCase();
              if (text) {
                this.countries.forEach(function (country) {
                  var index = country.toLowerCase().indexOf(text);
                  if (index !== -1) {
                    obj.push({
                      before: country.slice(0, index),
                      text: country.slice(index, index + text.length),
                      after: country.slice(index + text.length)
                    });
                  }
                });
              }
              return obj;
            }
          }
        });
      })(Vue);
    </script>
  </body>
</html>

上記のデモはこちらから。

この程度の機能だと記述量はjQueryの場合とさほど変わりませんが、HTML及びJavaScriptの担当する役割が明確に分かれており、拡張性はこちらの方が遥かに優れています。JavaScriptのコードに、HTMLが含まれることがなく、各々を疎結合に保つことができます。

jQueryとは違い、HTMLへの参照がルート要素の#appのみであり、それ以外のidやclassは変更しても影響を与えません。HTMLの構造が変わっても、JavaScript自体の動作が崩れることはまずありません。

そして、#appに対して動作しているので、#app以外のHTMLには全く影響しないので、jQueryや他のライブラリとの共存も可能です。これもVue.jsが最もオススメできる理由の一つです。

jQueryに慣れている方だと、初めはVue.jsのコードは読みにくいかもしれませんが、慣れれば役割が明確化している分、Vue.jsの方が早く理解できるコードになります。

また、Vue.jsの方で定義している変数appは、そこで定義しているデータをそのまま格納しています。つまり、console.log(app.text);と記述すればその時点のテキストボックスの内容、console.dir(app.countryListObjects);と記述すればその時点のリスト描画用のオブジェクトを参照できます。すなわち、デバッグが容易なのです。

他にも様々な機能があります。日本語ドキュメント英語ドキュメントを読めば、高機能であることと設計のシンプルさが理解しやすいのではと思います。

また、もっと高機能(フルスタック)に使いたい場合は、AjaxやルーティングのVue.js用のプラグインもあったりするので、一部分だけをVue.jsを使ってミニマムに実装するのはもちろん、SPAを丸々Vue.jsを使ってフルスタックに実装することもできるので、非常に様々な用途で利用できます。

SPAなどの大規模な用途では、Webpackを利用した「単一ファイルコンポーネント」という仕組みで、UIを機能ごとに複数ファイルに分割し、最終的にそれを1ファイルにビルド(コンパイル)するといったこともできます。

もはや、オブジェクト指向言語でクラスを定義するのと同じように機能やUIを拡張していけるのです。

まとめ

Vue.jsの使い方の説明なしで特徴を挙げていきましたので、わかりにくかったかもしれませんが、ソースコードの雰囲気は掴めてもらえたかな、と思います。

Vue.jsは、深く利用すればするだけ便利、ですが深く利用せずに最低限の学習コストで導入することもできる、そんなフレームワークです。

手軽に一部分だけの導入も可能なので、レガシーjQueryコードに悩まされている方々、ぜひ導入を検討してみてはいかがでしょうか。

Vue.js - The Progressive JavaScript Framework