ディレクティブ・リファレンス

ichigo.js のテンプレートで使えるディレクティブを、修飾子と使用例つきで網羅します。文法は Vue ライクなので、Vue 経験者ならそのまま読み進められます。

ディレクティブは要素の属性として記述します。v- で始まるものに加えて、よく使う 2 つには短縮記法があります。

記法 短縮 意味
v-bind:href="url" :href="url" 属性バインド
v-on:click="fn" @click="fn" イベントリスナ

実際に動くサンプルは「サンプル集」から確認できます。

テキスト補間 {{ }}

二重中括弧で式を埋め込みます。式の結果はテキストとして挿入され、HTML はエスケープされます。

<p>こんにちは、{{ user.name }} さん({{ items.length }} 件)</p>
<p>合計: {{ (price * quantity).toLocaleString() }} 円</p>

補間内には JavaScript の式を書けます(文は不可)。data / computed / methods、および $refs などのヘルパーを参照できます。

v-text / v-html

v-text は要素の textContent を、式の結果で置き換えます。{{ }} と違い要素の中身を丸ごと差し替えるため、初期表示のちらつきを避けたいときに向きます。

<span v-text="message"></span>
<!-- {{ message }} と同じ結果(HTML はエスケープされる) -->

v-htmlinnerHTML を設定し、生の HTML を描画します。

<div v-html="htmlContent"></div>

⚠️ セキュリティ: v-html は XSS の温床になり得ます。信頼できる内容にのみ使い、ユーザー入力を絶対に渡さないでください。プレーンテキストには v-text{{ }} を使います。

v-bind(:

属性・プロパティを式にバインドします。

<img :src="imageUrl" :alt="imageAlt">
<a :href="`/users/${user.id}`">プロフィール</a>
<button :disabled="isLoading">送信</button>

class のバインド

オブジェクト記法(キーが真のときクラスを付与)と配列記法が使えます。静的な class と併用すると両方がマージされます。

<!-- オブジェクト記法 -->
<div class="card" :class="{ active: isActive, 'is-done': todo.completed }"></div>

<!-- 配列記法 -->
<div :class="['badge', isError ? 'badge-error' : 'badge-ok']"></div>

style のバインド

<div :style="{ height: value * 2 + 'px', color: theme.ink }"></div>

v-if / v-else-if / v-else

条件付きでレンダリングします。条件が偽の要素は DOM に存在しません(生成・破棄される)。

<div v-if="count > 10">10 より大きい</div>
<div v-else-if="count > 5">5 より大きい</div>
<div v-else>5 以下</div>

v-else-if / v-else は、対応する v-if の直後の要素に置く必要があります。

v-show

v-show は要素を常に生成し、display の切り替えだけで表示・非表示を制御します。頻繁にトグルする要素は、生成・破棄を伴う v-if より v-show が軽量です。

<div v-show="isVisible">トグルできます</div>

v-for

配列・オブジェクト・数値・文字列を反復描画します。:key を付けると差分更新が安定します(推奨)。

<ul>
  <li v-for="(item, index) in items" :key="item.id">
    {{ index }}: {{ item.name }}
  </li>
</ul>

<template> でラッパーなし反復

ラッパー要素を増やさず複数ノードを繰り返したいときは、<template>v-for を置きます。

<dl>
  <template v-for="row in rows" :key="row.id">
    <dt>{{ row.term }}</dt>
    <dd>{{ row.description }}</dd>
  </template>
</dl>

<template> には v-for または v-if のどちらか一方のみを置けます(同じ <template> に両方は不可)。両方必要なときは入れ子にします。

<template v-for="item in items" :key="item.id">
  <div v-if="item.visible">{{ item.name }}</div>
</template>

v-for と v-if の併用

<template> 以外の通常要素では、同じ要素に v-forv-if を併用できます。v-for が先に評価され、各反復で v-if が判定されます。

<li v-for="item in items" v-if="item.visible" :key="item.id">{{ item.name }}</li>

v-on(@

イベントを購読します。ハンドラはメソッド名でも、インラインの式でも指定できます。

<button @click="handleClick">クリック</button>
<button @click="count++">インライン式も可</button>
<form @submit.prevent="handleSubmit">送信</form>

ハンドラのコンテキスト

すべてのイベントハンドラは、第 1 引数にイベント、第 2 引数に $ctx を受け取ります。

methods: {
  handleClick(event, $ctx) {
    // event       … DOM イベント
    // $ctx.element  … DOM 要素
    // $ctx.vnode    … VNode インスタンス
    // $ctx.userData … Proxy を介さないストレージ
  }
}

イベント修飾子

修飾子 効果
.stop stopPropagation()
.prevent preventDefault()
.capture キャプチャフェーズで購読
.self イベントの発生源が自身のときだけ実行
.once 一度だけ実行
<div @click.stop="onClick">伝播を止める</div>
<a @click.prevent="onNav">既定動作を抑止</a>

キー修飾子(KeyboardEvent)

.enter .tab .delete(Delete / Backspace).esc.escape.space .up .down .left .right

<input @keyup.enter="submit" @keyup.esc="cancel">

マウスボタン修飾子(MouseEvent)

.left .middle .right

<a @mousedown.middle="openInNewTab">ホイールクリック</a>
<div @contextmenu.prevent="onMenu">右クリックメニューを抑止</div>

システム修飾キー

.shift .ctrl .alt .meta.exact を加えると、ほかの修飾キーが押されていないことを要求します。

<button @click.shift="onShiftClick">Shift+クリック</button>
<button @click.ctrl.exact="onCtrlOnly">Ctrl だけのクリック</button>

v-model

フォーム要素との双方向バインドです。

<input v-model="message">
<textarea v-model="bio"></textarea>

修飾子

修飾子 効果
.lazy input ではなく change で同期
.number 数値に変換
.trim 前後の空白を除去
<input v-model.trim="username">
<input v-model.number="age" type="number">

要素ごとの挙動

<!-- チェックボックス(真偽値) -->
<input type="checkbox" v-model="isChecked">

<!-- 真偽値のカスタム化 -->
<input type="checkbox" v-model="status" :true-value="'yes'" :false-value="'no'">

<!-- 配列にバインド(複数選択) -->
<input type="checkbox" value="a" v-model="selected">
<input type="checkbox" value="b" v-model="selected">

<!-- ラジオ -->
<input type="radio" value="a" v-model="picked">
<input type="radio" value="b" v-model="picked">

<!-- セレクト(v-for で動的生成しても選択が再適用される) -->
<select v-model="choice">
  <option v-for="o in options" :key="o.value" :value="o.value">{{ o.label }}</option>
</select>
  • チェックボックス — 真偽値、:true-value / :false-value のペア、または配列(value を出し入れ)にバインド
  • ラジオ — 選択された value:value)にバインド
  • セレクト — 選択中の <option> の値にバインド

書き込み可能な computed{ get, set })を v-model の対象にすることもできます(「基本ガイド」参照)。

v-focus

フォーカス制御を宣言的に行います。フォーカスは requestAnimationFrame で遅延されるため、v-if 直後に現れた要素にも確実に当たります。

<!-- マウント後に一度フォーカス -->
<input v-focus>

<!-- フォーカス+全選択 -->
<input v-focus.select>

<!-- フォーカス+キャレットを末尾へ -->
<input v-focus.cursor-end value="prefilled">

<!-- 条件付き(偽→真に変わった瞬間に発火) -->
<input v-focus="isEditing">
  • 式なし — マウント後に一度だけフォーカス
  • 式あり — 偽 → 真の変化のときだけフォーカス(毎回の更新で再フォーカスしない)
  • 修飾子 .select(全選択)/ .cursor-end(末尾にキャレット)

ライフサイクルフック

要素のライフサイクルの各段階でコードを実行できます。@v-on)でフックを購読し、各フックは $ctxelement / vnode / userData)を受け取ります。外部ライブラリ連携やアニメーションの起点に使います。

<div v-if="show"
     @mount="onMount"
     @mounted="onMounted"
     @update="onUpdate"
     @updated="onUpdated"
     @unmount="onUnmount"
     @unmounted="onUnmounted">
  内容
</div>
フック タイミング
@mount DOM へのマウント直前
@mounted マウント直後
@update 更新直前
@updated 更新直後
@unmount 破棄開始の直前(要素はまだ DOM 上)
@unmounted 破棄完了後($ctx.element で参照は可能)

userData と自動クリーンアップ

$ctx.userData は、リアクティブプロキシの影響を受けない Map ストレージです。Chart.js などサードパーティのインスタンス保持に最適で、close() を持つオブジェクトは破棄時に自動でクリーンアップされます。

methods: {
  onMounted($ctx) {
    const chart = new Chart($ctx.element.querySelector('canvas'), { /* ... */ });
    $ctx.userData.set('chart', chart);
  },
  onUpdated($ctx) {
    $ctx.userData.get('chart')?.update();
  },
  onUnmount($ctx) {
    $ctx.userData.get('chart')?.destroy();
  }
}

破棄フェーズの順序は次のとおりです。

  1. @unmount 発火(要素はまだ DOM 上)
  2. userData の自動クリーンアップ(close() 呼び出し)
  3. 子ノードを再帰的に破棄
  4. 依存の登録解除
  5. ディレクティブマネージャの後始末
  6. @unmounted 発火(DOM からは外れているが $ctx.element は参照可能)

v-if での表示切替や v-for の各要素でもフックは発火します。動く例は「ライフサイクルフック」を参照してください。

Observer ディレクティブ

ブラウザ標準の Observer API を、ディレクティブとして宣言的に扱えます。いずれも破棄時に自動で disconnect され、ハンドラは $ctx を受け取ります。オプションは :options.<種別> または汎用の :options で渡します。

v-resize

ResizeObserver で要素サイズの変化を監視します。

<div v-resize="onResize" :options.resize="{ box: 'border-box' }">
  {{ width }}px × {{ height }}px
</div>
methods: {
  onResize(entries, $ctx) {
    const r = entries[0].contentRect;
    this.width = Math.round(r.width);
    this.height = Math.round(r.height);
  }
}

→ 動く例: Resize Observer

v-intersection

IntersectionObserver で要素の可視状態を検知します。遅延読み込み・無限スクロール・スクロール演出に。

<div v-intersection="onIntersection" :options.intersection="{ threshold: 0.5 }">
  {{ isVisible ? '見えている' : '見えていない' }}
</div>
methods: {
  onIntersection(entries, $ctx) {
    this.isVisible = entries[0].isIntersecting;
  }
}

→ 動く例: Intersection Observer

v-performance

PerformanceObserver でパフォーマンスエントリを観測します。ハンドラの引数は (entries, observer, options, $ctx) です。

<div v-performance="onPerformance" :options.performance="{ entryTypes: ['measure', 'mark'] }">
  計測対象
</div>
methods: {
  onPerformance(entries, observer, options, $ctx) {
    entries.getEntries().forEach(e => console.log(`${e.name}: ${e.duration}ms`));
  }
}

→ 動く例: Performance Observer

次のステップ