テンプレートはHTMLである

Vue.jsのテンプレート構文を学ぶにあたって,最も重要なことを先に言います.

Vueのテンプレートは,validなHTMLです.

JSXのようにJavaScriptの中にHTMLを書くのではありません.
HTMLの中に,ほんの少しだけVue固有の属性を足すだけです.
ブラウザのHTMLパーサーがそのまま解釈できる,正当なHTMLです.

これはVueの設計思想の根幹にある考え方です.

Mustache構文: {{ }}

データをテンプレートに表示するには {{ }} (Mustache構文) を使います.

まずは一番シンプルな例から.

<div id="app">
  <h1>{{ message }}</h1>
</div>

<script type="importmap">
  { "imports": { "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js" } }
</script>
<script type="module">
  import { createApp, ref } from "vue";

  createApp({
    setup() {
      const message = ref("My Bookshelf");
      return { message };
    },
  }).mount("#app");
</script>

{{ message }} の部分がsetup() で定義したmessageの値に置き換わります.
テンプレートの残りは ただのHTMLのままです.

v-for: 繰り返し

HTMLをコピペする代わりに,配列からリストを生成します.

<div id="app">
  <h1>My Bookshelf</h1>
  <p>最近読んだ本の記録です.</p>

  <div class="card" v-for="book in books">
    <h2>{{ book.title }}</h2>
    <p>{{ book.description }}</p>
    <span class="tag" v-for="tag in book.tags"> {{ tag }} </span>
  </div>
</div>

v-for="book in books" — これがVueのディレクティブです.
HTMLの属性として書きます.classidを書くのと同じ場所に書きます.

ここで注目すべきは,テンプレートの構造が前章の静的HTMLとほぼ同じ だということです.

静的HTML Vueテンプレート
<div class="card"> <div class="card" v-for="book in books">
<h2>リーダブルコード</h2> <h2>{{ book.title }}</h2>
<span class="tag">programming</span> <span class="tag" v-for="tag in book.tags">{{ tag }}</span>

HTMLの構造はそのまま.属性が一つ増えて,テキストが {{ }} になっただけ.
これがテンプレートDSL (Domain Specific Language)です.HTMLを壊さず,拡張しています.

v-bind: 属性のバインディング

テキストコンテンツだけでなく,HTML属性にもデータを反映できます.

<a v-bind:href="book.url">{{ book.title }}</a>

省略記法として : だけで書くこともできます.

<a :href="book.url">{{ book.title }}</a>

これもHTMLの属性 の位置に書いています.
特殊な構文ではなく,HTMLの属性の値にデータをバインドしているだけです.

v-if / v-else: 条件付きレンダリング

<div class="card" v-for="book in books">
  <h2>{{ book.title }}</h2>
  <p>{{ book.description }}</p>
  <p v-if="book.tags.length === 0">タグなし</p>
  <div v-else>
    <span class="tag" v-for="tag in book.tags"> {{ tag }} </span>
  </div>
</div>

v-ifもHTMLの属性です.hidden属性を書くような感覚で使えます.

完全なコード

ここまでの内容をまとめた完全なコードです.

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>My App</title>
    <style>
      body {
        font-family: sans-serif;
        max-width: 600px;
        margin: 40px auto;
        padding: 0 20px;
      }
      .card {
        border: 1px solid #e2e8f0;
        border-radius: 8px;
        padding: 20px;
        margin-bottom: 16px;
      }
      .card h2 {
        margin-top: 0;
      }
      .tag {
        display: inline-block;
        background: #edf2f7;
        border-radius: 4px;
        padding: 2px 8px;
        font-size: 0.85em;
        margin-right: 4px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <h1>My Bookshelf</h1>
      <p>最近読んだ本の記録です.</p>

      <div class="card" v-for="book in books">
        <h2>{{ book.title }}</h2>
        <p>{{ book.description }}</p>
        <span class="tag" v-for="tag in book.tags"> {{ tag }} </span>
      </div>
    </div>

    <script type="importmap">
      {
        "imports": {
          "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
        }
      }
    </script>
    <script type="module">
      import { createApp, ref } from "vue";

      createApp({
        setup() {
          const books = ref([
            {
              title: "リーダブルコード",
              description: "読みやすいコードを書くための実践的な技法.",
              tags: ["programming", "practices"],
            },
            {
              title: "プログラミング言語の基礎概念",
              description: "言語の設計を支える根本的な概念を学ぶ.",
              tags: ["cs", "language"],
            },
            {
              title: "Web ブラウザセキュリティ",
              description: "ブラウザに関するセキュリティを体系的に解説.",
              tags: ["security", "browser"],
            },
          ]);

          return { books };
        },
      }).mount("#app");
    </script>
  </body>
</html>

ブラウザで開くと,前章と まったく同じ見た目 です.
しかし裏側では,データとビューが分離されています.
本を追加したければ,books配列にオブジェクトを足すだけです.

テンプレートは「別言語」ではない

ReactのJSXはJavaScriptの拡張です.書くためにはBabelなどのトランスパイラが必要です.
Angularのテンプレートは独自の構文が多く,HTMLからはかなり離れています.

VueのテンプレートはHTMLそのもの に,いくつかの属性(v-for, v-if, v-bind)と {{ }} を足しただけです.

  • HTMLを書ける人なら,すぐに読める

  • ブラウザのDevToolsでそのまま見える

  • HTMLのバリデーターも(ほぼ)通る

テンプレートがHTMLであること — これはVueの最大の強みの一つです.

まとめ

  • VueのテンプレートはvalidなHTMLにディレクティブを足したもの

  • {{ }} でデータを表示する

  • v-forでリストを生成する(HTML属性として書く)

  • v-bind (:) で属性にデータをバインドする

  • v-if / v-elseで条件付き表示をする

  • テンプレートの構造は 静的HTMLとほぼ同じ まま

  • ファイルは依然としてindex.html一枚