[Vue.js] 3. 双方向データバインディングとその他のディレクティブ

3.1 双方向データバインディング

データバインディングのディレクティブとイベントのディレクティブを組み合わせると入力フォーム内のコントロールの値とテンプレート変数を双方向にバインディングすることが可能。

3.1.1 双方向データバインディングを実現する v-model

<script setup lang="ts">
import { ref } from 'vue'

const inputNameModel = ref('双方向')
</script>

<template>
  <section>
    <input type="text" v-model="inputNameModel" />
    <p>{{ inputNameModel }}</p>
  </section>
</template>

入力欄のタグにディレクティブv-modelを記述し、その属性値としてテンプレート変数を指定している。
これだけで入力内容とテンプレート変数が自動で連動する。

3.1.2 片方向のデータバインディング

なぜ双方向データバインディングが成り立つのか、原理を片方ずづ見ていく。

<script setup lang="ts">
import { ref } from 'vue'

const inputNameBind = ref('しんちゃん')
const inputNameOn = ref('ななし')

const onInputName = (event: Event): void => {
  const element = event.target as HTMLInputElement
  inputNameOn.value = element.value
}
</script>

<template>
  <section>
    <input type="text" v-bind:value="inputNameBind" />
  </section>
  <br />
  <selction>
    <input type="text" v-on:input="onInputName" />
    <p>{{ inputNameOn }}</p>
  </selction>
</template>

1つ目の入力欄にはテンプレート変数inputNameBindの値がディフォルト値として表示されている。
2つ目の入力欄に何か文字列を入れると、その下のマスタッシュ構文の内容が変化する。

入力コントロールから入力値を取得するコードについて
const onInputName = (event: Event): void => {
  const element = event.target as HTMLInputElement
  inputNameOn.value = element.value
}

コントロールからの入力値を取得するコードは、JavaScriptならば「event.target.value」と記述できるが、TypeScriptではイベントが発生したターゲット要素を表す event.target プロパティはEventTarget型でありここから直接入力値を表す value プロパティは取得できない。
いったんイベントが発生した input 要素を表す HTMLInputElement 型に変換する必要がある。
その後 HTMLInputElement 型の element から value プロパティの値(入力値)を取得する。

テンプレート変数→コントロール入力値(v-bind)

1つ目の入力欄は、テンプレート変数の値を入力コントロールのディフォルト入力値としている。
これには、v-bindを利用している。
v-bind:valueの属性値をテンプレート変数inputNameBindにするとこによって自動的に変数の値が入力コントロールのディフォルト値になる。

コントロール入力値→テンプレート変数(v-on:input)

2つ目の入力欄は、コントロールの入力値をテンプレート変数に格納している。
これは、イベントディレクティブv-on:inputを利用している。
v-on:inputのイベントハンドラメソッドonInputNameで入力された値を引数(イベントオブジェクト)として受け取り、テンプレート変数inputNameOnに格納している。
結果、マスタッシュ構文の内容が変化する。

v-bind と v-on:input の組み合わせ

v-bind と v-on:input を組み合わせることで双方バインディングが完成する。

<script setup lang="ts">
import { ref } from 'vue'

const inputName2Way = ref('双方向')

const onInputName2Way = (event: Event): void => {
  const element = event.target as HTMLInputElement
  inputName2Way.value = element.value
}
</script>

<template>
  <section>
    <input type="text" v-bind:value="inputName2Way" v-on:input="onInputName2Way" />
    <p>{{ inputName2Way }}</p>
  </section>
</template>

v-bindとv-on:inputで同一のテンプレート変数を用いることで双方向データバインディングを実現している。

これをシンプルに記述できるようにしたのが v-model である。
イベントハンドラを記述しなくても、v-modelの値としてテンプレート変数を指定しておけば、入力した値でテンプレート変数が更新される。

<input type="text" v-model="inputName2Way" />

この方法は、type 属性が email や number、data など、文字列入力に準ずるものであれば同じように利用できる。

3.1.4 文字列入力以外のコントロールでの v-model

  • テキストエリア
  • ラジオボタン
  • ドロップダウンリスト
  • チェックボックス
  • 複数選択セレクトボックス
<script setup lang="ts">
import { ref } from 'vue'

const inputTextarea = ref('テキストエリアへの入力文字。\n改行も加えてみる。')  // (1)
const memberType = ref(1)           // (2)
const memberTypeSelect = ref(1)     // (3)
const isAgreed = ref(false)         // (4)
const isAgreed01 = ref(0)           // (5)
const selectedOS = ref([])          // (6)
const selectedOSSelect = ref([])    // (7)
</script>

<template>
  <!-- (1) テキストエリア -->
  <textarea v-model="inputTextarea"></textarea>
  <p>{{ inputTextarea }}</p>
  <br />

  <!-- (2) ラジオボタン -->
  <section>
    <label><input type="radio" name="memberType" value="1" v-model="memberType" />通常会員</label>
    <label><input type="radio" name="memberType" value="2" v-model="memberType" />特別会員</label>
    <label><input type="radio" name="memberType" value="3" v-model="memberType" />優良会員</label>
    <br />
    <p>選択されたラジオボタン: {{mimberType}}</p>  <!-- value の値 -->
  </section>
  <br />

  <!-- (3) ドロップダウンリスト -->
  <section>
    <select v-model="memberTypeSelect">
      <option value="1">通常会員</option>
      <option value="2">特別会員</option>
      <option value="3">優良会員</option>
    </select>
    <br />
    <p>選択されたリスト: {{ memberTypeSelect }}</p>  <!-- value の値 -->
  </section>
  <br />

  <!-- (4) 単一チェックボックス(true/false) -->
  <section>
    <label><input type="checkbox" v-model="isAgreed" />同意する</label>
    <p>同意の結果: {{ isAgreed }}</p>  <!-- true/false -->
  </section>

  <!-- (5) 単一チェックボックス(値でデータ管理) -->
  <section>
    <label>
      <input type="checkbox" v-model="isAgreed01" true-value="1" false-value="0" />同意する
    </label>
    <p>同意の結果: {{ isAgreed01 }}</p>  <!-- true-value 又は false-value の値 -->
  </section>
  <br />

  <!-- (6) 複数選択チェックボックス -->
  <section>
    <label><input type="checkbox" v-model="selectedOS" value="1" />macOS</label>
    <label><input type="checkbox" v-model="selectedOS" value="2" />Windows</label>
    <label><input type="checkbox" v-model="selectedOS" value="3" />Linux</label>
    <label><input type="checkbox" v-model="selectedOS" value="4" />iOS</label>
    <label><input type="checkbox" v-model="selectedOS" value="5" />Android</label>
    <p>選択されたOS: {{ selectedOS }}</p>  <!-- value の値の配列 例)["1", "3", "5"] -->
  </section>

  <!-- (7) 複数選択セレクトボックス -->
  <section>
    <select v-model="selectedOSSelect" multiple>
      <option value="1">macOS</option>
      <option value="3">Windows</option>
      <option value="4">Linux</option>
      <option value="5">iOS</option>
      <option value="6">Android</option>
    </select>
    <p>選択されたOS: {{ selectedOSSelect }}</p> <!-- value の値の配列 例)["1", "3", "5"] -->
  </section>
</template>

テキストエリア(textarea)

<textarea v-model="inputTextarea"></textarea>

開始タグと終了タグには何も記述されていないがテンプレート変数 inputTextarea の値が表示される。

マスタッシュ構文を使用すると(<textarea …>{{inputTextarea}}</textarea>)初期値は表示されるがリアクティブにならないので注意する。

ラジオボタン(input type=”radio”)

    <label><input type="radio" name="memberType" value="1" v-model="memberType" />通常会員</label>
    <label><input type="radio" name="memberType" value="2" v-model="memberType" />特別会員</label>
    <label><input type="radio" name="memberType" value="3" v-model="memberType" />優良会員</label>

グループ内の全てのコントロールに v-model の属性値として同一のテンプレート変数を指定する。

ドロップダウンリスト(option)

    <select v-model="memberTypeSelect">
      <option value="1">通常会員</option>
      <option value="2">特別会員</option>
      <option value="3">優良会員</option>
    </select>

selectタグに v-model ディレクティブを記述する。

単一チェックボックス(input type=”checkbox”)

チェックボックスを1個だけで使用する場合、値を true/false で管理する。

<label><input type="checkbox" v-model="isAgreed" />同意する</label>

true/false 以外の値でデータを管理したい場合は true-value 属性と false-value 属性を利用する。

<label><input type="checkbox" v-model="isAgreed01" true-value="1" false-value="0" />同意する</label>

複数選択チェックボックス(input type=”checkbox”)

    <label><input type="checkbox" v-model="selectedOS" value="1" />macOS</label>
    <label><input type="checkbox" v-model="selectedOS" value="2" />Windows</label>
    <label><input type="checkbox" v-model="selectedOS" value="3" />Linux</label>
    <label><input type="checkbox" v-model="selectedOS" value="4" />iOS</label>
    <label><input type="checkbox" v-model="selectedOS" value="5" />Android</label>

全てのチェックボックスに v-model の属性値として同一のテンプレート変数を指定すると、そのテンプレート変数が配列として扱えるようになる。

選択されたOS:["1", "3", "4"]

複数選択セレクトボックス (option)

    <select v-model="selectedOSSelect" multiple>
      <option value="1">macOS</option>
      <option value="3">Windows</option>
      <option value="4">Linux</option>
      <option value="5">iOS</option>
      <option value="6">Android</option>
    </select>

複数選択チェックボックスと同様に配列の形でデータを扱う。

ファイル選択コントロールの双方向データバインディング(input type=”file”)

input タグのうち、type=”file”(ファイル選択コントロール)の場合は v-model は使えない。
アプリケーション側からコントロールに値をバインドすることがセキュリティ上許可されtおらず、コントロールで選択されたファイルの受け取りだけがアプリケーションの仕事になるので双方向データバインディングという概念が成り立たない。

3.1.5 v-model の修飾子

修飾子内容
lazyinput の代わりに change イベントで双方向データバインドを行う
number入力値を数値として扱う
trim入力値の前後の余分な空白を取り除く

.lazy

type=”text” の input タグへの双方向データバインディングは1文字入力/消去するたびにデータバインディングが行われ、マスタッシュ構文による表示部分でも即座に値が反映される。
これを一通り入力を終えて、入力コントロールからフォーカスが離れるのを待ってからデータバインディングを行うようにするのが .lazy 修飾子

.number

input type=”number” のように数値を入力するコントロールに有効。
このようなコントロールでは数値で入力しても入力値は原則文字列(string型)データとなるが TypeScript側では数値は number型の変数に格納する必要がある。
そこで、文字列として入力されて数値データを number型に変換したうえで nunber型のテンプレート変数に格納してくれるのが .number 修飾子。

.trim

前後の空白が取り除かれた上でデータバインドが行われる。

<script setup lang="ts">
import { ref } from 'vue'

const trimedText = ref('')
</script>

<template>
  <section>
    <input type="text" v-model.trim="trimedText" />
    <p>入力文字列: {{ trimedText }}</p>
  </section>
</template>

3.2 その他のデータバインディングのディレクティブ

3.2.1 HTML 文字列をそのまま表示する v-html

マスタッシュ構文などテンプレートにデータをバインドする場合、Vueでは自動的にHTMLエスケープされるようになっている。
HTML文字列をそのまま表示する場合にりょうするのが v-heml ディレクティブ。

<script setup lang="ts">
import { ref } from 'vue'

const htmlStr = ref('<a href="https://vue.js.org//">Vue.jsのTOPページ</a>')
</script>

<template>
  <section>{{ htmlStr }}</section>       <!-- HTMLエスケープされる -->
  <section v-html="htmlStr"></section>   <!-- そのまま表示される -->
</template>

v-html ディレクティブはHTML文字列をHTML文字列のままテンプレートに埋め込むため、XSS(クロスサイトスプリプティング)の脆弱性を含んでします可能性があるので、v-htmlを利用して表示するのはプログラマが直接用意したデータなど信用できるものに限定し、それ以外のデータが渡されることがないようにする。

3.2.2 静的コンテンツとして表示させる v-pre

v-pre ディレクティブは、マスタッシュ構文も含め配下のタグ内のテンプレート記述を全て無効化し、そのまま表示するディレクティブ。

<script setup lang="ts">
</script>
<template>
  <section v-pre>
    <p v-on:click="showHello">{{ hello! }}</p>  <!-- そのまま「{{ hello! }}」と表示される
  </section>
</template>
<section>
  <p v-onclick="showHello">{{ hello! }}</p>
</section>

2.2.3 データバインドを一度だけ行う v-once

v-once ​ 要素やコンポーネントを一度だけレンダリングし、その後の更新はスキップする。
その後の再レンダリングでは、要素/コンポーネントとそのすべての子要素は静的コンテンツとして扱われ、スキップされる。
これは、更新のパフォーマンスを最適化するために使用できる。

<script setup lang="ts">
import { ref } from 'vue'

const price = ref(1000)
</script>

<template>
  <section>
    <input type="number" v-model="price" />円<br />
    <p>金額は{{ price }}円です。</p>         <!-- 入力のたびに値が反映される -->
    <p v-once>金額は{{ price }}円です。</p>  <!-- 2回目以降の入力値は反映されない。初期値のまま -->
  </section>
</template>

3.2.4 レンダリング終了までマスタッシュ構文を非表示にする v-cloak

テンプレートブロックに記述されたマスタッシュ構文は、HTMLが読み込まれた後ブラウザで動作するJavaScriptによって値が埋め込まれるので、場合によっては値が埋め込まれるまでの一瞬の間にマスタッシュ構文がそのまま表示されてしまうことがある。
これを防ぎたい場合は、そのタグに v-cloak ディレクティブを記述する。

<script setup lang="ts">
import { ref } from 'vue'

const hello = ref('こんにちは!')
</script>

<template>
  <p v-cloak>{{ hello }}</p>
</template>

<style>
[v-cloak] {
  display: none;
}
</style>

v-cloak ディレクティブは最初は属性としてそのままレンダリングされるが、マスタッシュ構文のレンダリングが終了したタイミングで Vue によって取り除かれる。

上記コードでは、v-cloakが付いている最初はスタイルブロックに定義されている[v-cloak]のスタイルが適用されタグそのものが非表示になる。
マスタッシュ構文のレンダリングが終了すると v-cloak が取り除かれ[v-cloak]のスタイルが適用されなくなるので表示される。

コメント

タイトルとURLをコピーしました