こんにちは、かつコーチです。
前回はViteでVueの開発環境を作りました。
今回は、.vueファイルの<template>部分に書く、Vue独自のテンプレート構文を解説していきます。
ここを押さえると、Vueらしいコードがぐっと読み書きしやすくなります。
テンプレート構文とは?
HTMLに近い書き方でUIを組み立てる
テンプレート構文とは、<template>タグの中に、通常のHTMLに近い書き方でUIを組み立てる、Vue独自の記法のことです。
<script setup lang="ts">
const message = "こんにちは、かつコーチです!";
</script>
<template>
<p>{{ message }}</p>
</template>
<script setup lang="ts">の中で定義した変数を、<template>の中でそのまま使える点が最大の特徴です。
Reactの場合、JSXの中に{変数名}と書いてJavaScriptの値を埋め込みますが、Vueでは{{ }}という書き方をします。
なぜHTMLに近い書き方ができるのか
VueのテンプレートはコンパイルされてJavaScriptの描画処理に変換されますが、見た目上はHTMLの延長として書けるように設計されています。
これにより、HTML・CSSの知識がある人であれば、JSXよりも直感的にUIのコードを読めることが多いです。
私自身、初めてVueのテンプレートを見たときに「これ、ほぼHTMLじゃん」と感じて、心理的なハードルがぐっと下がったのを覚えています。
Mustache構文:{{ }}で値を表示する
基本の書き方
変数の値をテンプレート内に表示するには、{{ }}(二重波括弧)で囲みます。
この書き方をMustache構文(口ひげのような見た目から名付けられた)と呼びます。
<script setup lang="ts">
const userName = "かつコーチ";
const age = 30;
</script>
<template>
<p>名前:{{ userName }}</p>
<p>年齢:{{ age }}歳</p>
</template>
{{ }}の中には、単純な変数だけでなく、簡単な式も書けます。
<script setup lang="ts">
const price = 1000;
</script>
<template>
<p>税込価格:{{ price * 1.1 }}円</p>
</template>
ただし、{{ }}の中に書けるのは1行で完結する簡単な式までです。
複雑な計算や条件分岐は、後の記事で扱う算出プロパティ(computed)にまとめるのがVueの基本的な作法です。
ディレクティブ:HTMLタグに機能を追加する
ディレクティブとは
ディレクティブとは、HTMLタグにv-から始まる特別な属性を追加することで、Vueに特別な振る舞いを指示する仕組みです。
代表的なディレクティブを見ていきましょう。
v-bind:属性に変数を渡す
HTML属性の値に変数を渡したいときは、v-bindを使います。
<script setup lang="ts">
const imageUrl = "/images/profile.png";
const isDisabled = true;
</script>
<template>
<img v-bind:src="imageUrl" alt="プロフィール画像" />
<button v-bind:disabled="isDisabled">送信</button>
</template>
v-bind:属性名という書き方をよく使うため、:属性名という省略記法も用意されています。
<template>
<img :src="imageUrl" alt="プロフィール画像" />
</template>
実務では、この省略記法の方が圧倒的によく使われます。
v-if / v-show:条件によって表示を切り替える
条件によって要素の表示・非表示を切り替えたいときは、v-ifまたはv-showを使います。
<script setup lang="ts">
const isLoggedIn = true;
</script>
<template>
<p v-if="isLoggedIn">ログイン中です</p>
<p v-else>ログインしてください</p>
</template>
Reactでは{isLoggedIn && <p>ログイン中です</p>}のように、JavaScriptの論理演算子で条件分岐を表現していましたが、Vueではv-if・v-elseという専用のディレクティブで表現します。
v-ifは条件がfalseのときDOM自体を作らないのに対し、v-showはdisplay: noneでCSS上だけ非表示にする、という違いがあります。
頻繁に表示・非表示を切り替える要素にはv-show、めったに切り替わらない要素にはv-ifを使う、という使い分けが基本です。
v-for:リストを繰り返し表示する
配列のデータを一覧表示したいときは、v-forを使います。
<script setup lang="ts">
const todos = ["買い物に行く", "ブログを書く", "運動する"];
</script>
<template>
<ul>
<li v-for="(todo, index) in todos" :key="index">{{ todo }}</li>
</ul>
</template>
Reactのtodos.map((todo, index) => <li key={index}>{todo}</li>)と同じ役割です。
:key(Reactでいうkeyprops)を必ず指定して、各要素を一意に識別できるようにするのは、Reactと同じく重要なルールです。
つまずきやすいポイント:v-forとv-ifの同時使用
意図しない挙動になりやすい組み合わせ
私が実際につまずいた経験として、リストの中で条件付き表示をしたいときに、v-forとv-ifを同じ要素に書いてしまい、想定と違う挙動になったことがあります。
❌ Before:同じ要素にv-forとv-ifを両方書く
<script setup lang="ts">
type Todo = { id: number; title: string; completed: boolean };
const todos: Todo[] = [
{ id: 1, title: "買い物に行く", completed: false },
{ id: 2, title: "ブログを書く", completed: true },
];
</script>
<template>
<ul>
<!-- 未完了のTODOだけ表示したいつもりが… -->
<li v-for="todo in todos" v-if="!todo.completed" :key="todo.id">
{{ todo.title }}
</li>
</ul>
</template>
Vue3ではv-ifの方がv-forより優先度が高く評価されるため、todoという変数がまだ定義されていない状態でv-ifが評価され、エラーになったり意図しない結果になったりします。
✅ After:<template>タグで分けるか、事前にフィルタしておく
<script setup lang="ts">
import { computed } from "vue";
type Todo = { id: number; title: string; completed: boolean };
const todos: Todo[] = [
{ id: 1, title: "買い物に行く", completed: false },
{ id: 2, title: "ブログを書く", completed: true },
];
// あらかじめ未完了のTODOだけに絞り込んでおく
const incompleteTodos = computed(() => todos.filter((todo) => !todo.completed));
</script>
<template>
<ul>
<li v-for="todo in incompleteTodos" :key="todo.id">
{{ todo.title }}
</li>
</ul>
</template>
Vue公式ドキュメントでも、v-forとv-ifを同じ要素に併用しないことが明確に推奨されています。
「表示するデータを絞り込む処理」と「絞り込んだデータを繰り返し表示する処理」を分けて考える癖をつけると、このつまずきを防げます。
computedについては、次々回の記事で詳しく解説します。
まとめ
この記事のポイント
{{ }}(Mustache構文)で、変数や簡単な式をテンプレートに表示できるv-bind(省略形:)で、HTML属性に変数を渡せるv-if/v-showで条件による表示切り替え、v-forでリストの繰り返し表示ができるv-ifとv-showは、DOMごと消すか見た目だけ隠すかという役割が異なるv-forとv-ifは同じ要素に併用せず、computedなどで事前に絞り込んでおくのが安全
次に読むべき記事
Reactの経験がある方は、「JSXではこう書いていたのに」という部分が色々気になってきた頃だと思います。
次回は、ReactとVueの記法をまとめて対応表として整理していきます。
→ 次の記事:Reactを知っている人向け:ReactとVueの記法対応表