ちょっと話題の記事

サーバーサイドエンジニアがじっくり学ぶVue.jsチュートリアル【2. Introduction】

こんにちは。DA事業本部の春田です。

普段の業務では全く扱わないフロントエンドの世界に飛び込んでみたいと思います。最近3系が登場しましたがまだ情報が少ないので、2系のチュートリアルを進めていきます。サンドウィッチマンでいう「ちょっと何言ってるかわからない」ポイントは、その都度調べていきます。英語版の公式ドキュメントがベースです。

今回はIntroduction編です。

Vue.js とは?

Vue.jsは、ユーザーインターフェース(UI)を構築するためのプログレッシブ・フレームワークです。

Unlike other monolithic frameworks, Vue is designed from the ground up to be incrementally adoptable.

このモノシリックなフレームワークが何を指しているかといえば、例えばAngular.jsです。モノシリックなフレームワークはWeb開発に必要な機能が一通り含まれているため、開発の立ち上がりは早いですが、機能間の依存関係が深いので、規模が大きくなるにつれて扱いが難しくなってきます。Vue.jsはそれとは対象的なモジュラー型のフレームワークで、View層を中心としたライブラリが揃っています。疎結合で機能の追加がしやすく、既存のWebアプリにも導入しやすい分、一から開発する場合には必要なライブラリを自分で調達する必要があります。

Vue is also perfectly capable of powering sophisticated Single-Page Applications when used in combination with modern tooling and supporting libraries

よくVue.jsはSPA(Single-Page Applications)用のフレームワークだと目にしますが、Vue.js単体ではSPAは実現できなかったんですね。View層を担うVue.jsと、他のライブラリを組み合わせることでSPAが実現可能になるみたいです。ここ勘違いしてました。

参照

Declarative Rendering

The official guide assumes intermediate level knowledge of HTML, CSS, and JavaScript. If you are totally new to frontend development, it might not be the best idea to jump right into a framework as your first step - grasp the basics then come back!

HTMLやCSS、JavaScriptの基本がわかってないような輩は出直して来い!とのことですが、ごめんなさい無視します。わからなくなった都度確認するスタイルで行きます。

Vue.jsを試す一番簡単な方法は、Hello World Exampleを試すとのことで、加筆修正したスクリプトを以下に記載します。

<!DOCTYPE html>
<html>
<head>
  <title>My first Vue app</title>
  <script src="https://unpkg.com/vue"></script>
</head>
<body>
  <div id="app">
    {{ message }}
  </div>
  <div id="app-2">
    <span v-bind:title="title">
      Hover your mouse over me for a few seconds
      to see my dynamically bound title!
    </span>
  </div>
  <script>
    var app = new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue!'
      }
    })
    var app2 = new Vue({
      el: '#app-2',
      data: {
        title: 'You loaded this page on ' + new Date().toLocaleString()
      }
    })
  </script>
</body>
</html>

上記ではまず、headタグに含まれているscriptタグで、unpkg.comのCDNからVue.jsのソースコードを取得しています。

At the core of Vue.js is a system that enables us to declaratively render data to the DOM using straightforward template syntax

DOM、厳密に何なのかはわかっていなかった用語なので調べてみます。MDNに良い表現があったので引用します。

The Document Object Model (DOM) is a programming interface for HTML and XML documents. It represents the page so that programs can change the document structure, style, and content.

DOMとは、プログラムでドキュメントの構造やスタイル、内容を変更できるようなページを表現するための、プログラミングインターフェイスです。Vue.jsの例で言うと、{{ message }}がインターフェイスとなって、プログラム側で内容を制御できるようになるわけですね。Vue.jsでは、{{ message }}宣言的レンダリング(Declarative Rendering)を行うためのテンプレートとされています。messageには、appという変数名で生成したVueインスタンスのdataオブジェクト内にある、messageの値がレンダリングされます。なので、ブラウザのJavaScriptコンソールからapp.message = 'Awesome'のように値を変更すれば、ブラウザ画面上のテキストもAwesomeへ更新される仕組みなっています。

プロパティtitleがレンダリングする先は、span v-bind:title="title"というHTMLタグの属性となっていますが、v-bind属性はVue.jsによって提供された特別な属性、ディレクティブ(directive)の一つです。上のスクリプトだと、「title属性にapp2のプロパティtitleの値をレンダリングする」ということになりますね。v-bindでレンダリングした場合も、ブラウザのJavaScriptコンソールからtitleプロパティの値の変更は可能です。

参照

Conditionals and Loops

次は条件分岐とループです。以下のスクリプトをサンプルとします。

<!DOCTYPE html>
<html>
<head>
  <title>My first Vue app</title>
  <script src="https://unpkg.com/vue"></script>
</head>
<body>
  <div id="app-3">
    <span v-if="seen">Now you see me</span>
  </div>
  <div id="app-4">
    <ol>
      <li v-for="todo in todos">
        {{ todo.text }}
      </li>
    </ol>
  </div>
  <script>
    var app3 = new Vue({
      el: '#app-3',
      data: {
        seen: true
      }
    })
    var app4 = new Vue({
      el: '#app-4',
      data: {
        todos: [
          { text: 'Learn JavaScript' },
          { text: 'Learn Vue' },
          { text: 'Build something awesome' }
        ]
      }
    })
  </script>
</body>
</html>

今度は、v-ifv-forという新しいディレクティブが登場してきました。v-ifにはseenというプロパティが対応しており、truefalseの二値が入ることで、要素を消したり表示したりすることができます。このような構造(Structure)の変化を及ぼすディレクティブに対しては、CSSで定義するトランジションエフェクト(遷移効果)を別途付与することができます。

上記v-forでは、todosというapp4のプロパティ内の配列データをループで回し、{{ todo.text }}をインターフェースにしてli要素を複数作成しています。コンソールからapp4.todos.push({ text: 'New item' })を実行すれば、新しい行を追加することも可能ですね。

Handling User Input

次はブラウザからの入力を扱うディレクティブの紹介です。以下のスクリプトをサンプルとします。

<!DOCTYPE html>
<html>
<head>
  <title>My first Vue app</title>
  <script src="https://unpkg.com/vue"></script>
</head>
<body>
  <div id="app-5">
    <p>{{ message }}</p>
    <button v-on:click="reverseMessage">Reverse Message</button>
  </div>
  <div id="app-6">
    <p>{{ message }}</p>
    <input v-model="message">
  </div>
  <script>
    var app5 = new Vue({
      el: '#app-5',
      data: {
        message: 'Hello Vue.js!'
      },
      methods: {
        reverseMessage: function () {
          this.message = this.message.split('').reverse().join('')
        }
      }
    })
    var app6 = new Vue({
      el: '#app-6',
      data: {
        message: 'Hello Vue!'
      }
    }) 
    </script>
</body>
</html>

To let users interact with your app, we can use the v-on directive to attach event listeners that invoke methods on our Vue instances

イベントリスナーとは、ブラウザからのクリックや入力といったイベントを追跡して、イベントに応じて特定の処理(関数)を実行するための仕組みのことです。通常は、要素に対してaddEventListener()メソッドを用いることで、対象の要素でイベントを検知できるようになるのですが、そのあたりのJavaScriptのコードをVue.jsではv-onディレクティブとVueインスタンスでラッピングして使いやすくしているのです。div id="app-5"要素がクリックされると、v-on:clickに紐づけられたreverseMessageが起動し、messageデータの加工を行います。reverseMessage関数は、Vueインスタンスのmethodsというオブジェクトの中で定義され、reverseMessage関数の中では、thisでVueインスタンス自身のmessageを呼んできています。このthis自体は奥が深そうですが、アロー関数とやらで定義してしまうと、thisdataオブジェクト内のプロパティを取得できなくなってしまうみたいです。詳しくは参照のリンクから。

v-modelディレクティブでは、インプットとアプリケーションステートを繋げる双方向バインディング(two-way binding)を実現できます。双方向バインディングでは、上の例ではフォームに入力した文字を検知して、dataオブジェクトのmessageの値を更新してくれます。JavaScriptのMutationObserverなどで実装していくのと比べれば、かなり簡単ですね。

参照

Composing with Components

Vue.jsで重要な概念の一つに、コンポーネントシステムというものがあります。「小さく、自己完結的で、再利用可能なコンポーネント」を組み合わせて実装していくのがVue.jsのお作法となります。コンポーネントの中には、データを受ける引数的な役割のpropsやHTMLデータのtemplateを記述でき、以下の例ではpropstodo属性にgroceryListの要素をv-bindすることで、アイテムをリスト表示しています。コンポーネントは、exportとimportを駆使して他のコンポーネントからでも使えるようにすることで、規模が大きく、機能が豊富なWebアプリケーションを構築するときにより力を発揮します。

<!DOCTYPE html>
<html>
<head>
  <title>My first Vue app</title>
  <script src="https://unpkg.com/vue"></script>
</head>
<body>
  <div id="app-7">
    <ol>
      <todo-item
        v-for="item in groceryList"
        v-bind:todo="item"
        v-bind:key="item.id"
      ></todo-item>
    </ol>
  </div>
  <script>
    Vue.component('todo-item', {
      props: ['todo'],
      template: '<li>{{ todo.text }}</li>'
    })
    var app7 = new Vue({
      el: '#app-7',
      data: {
        groceryList: [
          { id: 0, text: 'Vegetables' },
          { id: 1, text: 'Cheese' },
          { id: 2, text: 'Whatever else humans are supposed to eat' }
        ]
      }
    })
  </script>
</body>
</html>

Relation to Custom Elements

Vue components are very similar to Custom Elements, which are part of the Web Components Spec.

Web Components Specという単語が出てきました。Web Components Specとは、JavaScriptが提供する公式のコンポーネント機能であり、Custom ElementsShadow DOMHTML Templatesの3つの技術から構成されています。Vue.jsやReactがなくても、コンポーネントベースのWebサイトは実装できてしまう、ということですね。

Custom Elements(カスタム要素)はWeb Components Specの一つであり、その名の通り、自分で好きな要素を定義して使用することができるAPIで群、以下のように属性を引数的な感じに使用できます。確かにVue.jsのコンポーネントの仕様と似ていますね。

<script>
class TimeFormatted extends HTMLElement { // (1)

  connectedCallback() {
    let date = new Date(this.getAttribute('datetime') || Date.now());

    this.innerHTML = new Intl.DateTimeFormat("default", {
      year: this.getAttribute('year') || undefined,
      month: this.getAttribute('month') || undefined,
      day: this.getAttribute('day') || undefined,
      hour: this.getAttribute('hour') || undefined,
      minute: this.getAttribute('minute') || undefined,
      second: this.getAttribute('second') || undefined,
      timeZoneName: this.getAttribute('time-zone-name') || undefined,
    }).format(date);
  }

}

customElements.define("time-formatted", TimeFormatted); // (2)
</script>

<!-- (3) -->
<time-formatted datetime="2019-12-01"
  year="numeric" month="long" day="numeric"
  hour="numeric" minute="numeric" second="numeric"
  time-zone-name="short"
></time-formatted>

(出典:Custom elements

For example, Vue components implement the Slot API and the is special attribute.

例で上げられているSlot APIとは、HTML Templatesで用意されている要素の一つで、slotを使用するとテンプレートで定義したテキストを変更することが可能です。ただし、これらのWeb Components Specの機能はまだ未対応のブラウザもあるため、Vue.jsではWeb Components Specに対応していないブラウザでもコンポーネントが機能するように設計されています。また、Vue.jsのコンポーネントシステムは、Web Components Specにはない以下のような機能も追加されています。

  • most notably cross-component data flow
  • custom event communication
  • build tool integrations

この辺りは後の章で触れられそうなので今回はスキップします。

参照

おわりに

Vue.jsの手軽さがよくわかるイントロダクションでしたね。Vue.jsを切り口に、JavaScriptの仕様が結構わかってきたのも嬉しいです。

前回

次回

Comming Soon!