Vue.jsの概念に、
双方向データバインディングというものがあります。
これは、単方向データバインディングに対するものです。
本記事では、双方向データバインディングの解説を通して、
単方向データバインディングとの違いに触れつつ、
v-model, v-on, v-bindの関係性や違いについても解説します。
文字ではなく、動画で学習したい方は、
下記の動画御覧ください。
双方向データバインディングと単方向データバインディング
そもそも、データバインディングとは、何かっていうと、
これは、簡単に言うと、JavaScriptのデータと、
表示されているHTMLのデータとを合わせる仕組みのことです。
バックエンドとかだと、
あまりHTMLと合わせるみたいなシチュエーションがないので、
どちらかというと、フロントエンド技術の用語のような気もします。
さて、そんなデータバインディングですが、
バインディングされていないと、
Javascriptのデータをいくら内部で書き換えても、
画面の表示(HTML)が変わらないので、
データが変わっていることに気づけません。
イメージとしては、
Amazonのサイトとかで、
内部のJavaScriptで、商品の価格を1000円引きしているのに、
データバインディングされていないので、画面上では、
割引されていないように見えるような感じです。
(実際のapi処理では、JavaScriptのデータをバックエンドにわたすので、
仮に決済処理を行えば、割引価格になっているはずです)
そのため、画面を操るフロントエンドでは、
必須の技術なのですが、
こうしたバインディングにも、
単方向と、双方向という種類があります。
これらは、文字通りベクトルの違いです
フロントエンドのデータバインディングの場合、
大まかな流れとして
・JS → HTML
・HTML → JS
二種類があります。
双方向データバインディングとは、
この両方の流れをサポートしているバインディングのことです。
これに対して、単方向データバインディング、
上記のどちらか一方だけをサポートしているものです。
以下は、双方向データバインディングのサンプルコードです。
<template>
<div>
<label for="name">Name:</label>
<input id="name" v-model="name" />
<p>Hello, {{ name }}!</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const name = ref('');
</script>
これは、<input>タグの中身になにか入れると、
<p>タグの中身も同じものが表示されます。
これを実現しているのは、
v-modelというディレクティブなのですが、
(ディレクティブについては後で説明します)
流れとしては、下記のような感じです。
- <input>タグ(HTML) → name変数(JS)
- name変数(JS) → <p>タグ(HTML)
このようにして、
双方向にデータがバインディングされています。
イメージとしては、データが一周しているような感じでしょうか。
ディレクティブとは?
さて次に、ディレクティブとはなんでしょう?
これは、HTMLで記述する、
v-から始まる属性のことです。
ビルトインのディレクティブについては、
下記に記載があります。
https://ja.vuejs.org/api/built-in-directives.html
例えば、以下のようなものがあります。
v-bind | バインディング機能 |
v-on | イベント処理 |
v-model | 双方向データバインディング |
v-html | htmlを表示する |
v-if | if文 |
v-for | for文 |
他にも、v-onceとかv-preみたいなディレクティブもありますが、
使用頻度はあまり高くないので割愛します。
上記のビルトインディレクティブの一覧ドキュメントを御覧ください。
さて、この中で圧倒的によく使うのは、
v-bind、v-on、v-modelの3つで、
特にv-modelはよく使います。
以下、順番に解説します。
v-bind
先程、v-modelは双方向データバインディングだと説明しましたが、
v-modelの機能は、実はv-bindとv-onの複合で実現できます。
すなわち、v-bind単体だと、単方向データバインディングにあたるわけです。
それも方向的には、
JS → HTMLの方向です。
(v-onはこれの逆で、これらの複合で双方向データバインディングになります)
サンプルコードを紹介します。
<template>
<div>
<a :href="linkUrl">{{ linkText }}</a>
<button :class="buttonClass" @click="onClick">{{ buttonText }}</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const linkUrl = ref('https://example.com');
const linkText = ref('Example Link');
const buttonClass = ref('primary');
const buttonText = ref('Click me!');
function onClick() {
buttonText.value = 'Clicked!';
}
</script>
:classとか:hrefというのが、
v-bindの省略形の記法で、
v-bind:classとかと同じ意味です。
この場合、JS側で指定した、
linkUrlとかbuttonClass変数の内容が、
href属性やclass属性に適応されます。
v-on
v-onは、v-bindの逆の方向で、
HTML → JS
の流れです。(正確にはHTMLというよりDOMだが
templateタグからscriptタグという流れの表現的には、
HTMLと言っても良い気がしたのでHTMLとしています)
サンプルコードを紹介します。
<template>
<div>
<h1>タグ</h1>
<input type="text" @input="onInput" :value="message" />
<p>{{ message }}</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const message = ref('');
function onInput(event: Event) {
const target = event.target as HTMLInputElement;
message.value = target.value;
}
</script>
@inputというのは、
v-on:inputと同じ意味の省略形です。
inputイベントは、何かが入力されることをトリガーにします。
つまり、上記のコードは、inputタグ(HTML)に何かが入力されたら、
JS側のonInputメソッドが呼ばれ、message変数が書き換わるという、
バインディングをしています。
v-model
v-modelは、すでに出てきたv-bindとv-onの融合系みたいな感じです。
例えば、先程紹介した下記のコードですが、
<template>
<div>
<label for="name">Name:</label>
<input id="name" v-model="name" />
<p>Hello, {{ name }}!</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const name = ref('');
</script>
下記のように書き換えられます。
<template>
<div>
<label for="name">Name:</label>
<input id="name" :value="name" @input="updateName" />
<p>Hello, {{ name }}!</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const name = ref('');
function updateName(event: Event) {
const target = event.target as HTMLInputElement;
name.value = target.value;
}
</script>
記述が冗長になりましたが、同じことをしています。
イメージとしては、v-bind + v-on = v-modelみたいな感じです。
双方向データバインディングをしたい場合は、
基本的にv-modelを使うことが多いでしょう。
単方向データバインディングと双方向データバインディングどっちがいい?
これは、正直好みやケース・バイ・ケースの部分もあるのですが、
最近の主流は、やはり単方向データバインディングでしょうか。
というのも、双方向データバインディングの場合、
小規模のアプリとか、機能が単純なものであれば、
便利な代物ですが、複雑化かつ大規模になると、
データの流れが混戦しがちで、追うのも容易ではなくなってきます。
値があっちこっちにいったりきたりで、
結局どこ行った?状態になりがちなので、
双方向データバインディングは、一般的には、
メリットよりもデメリットのほうに注目されがちです。
また、意図せずデータが書き換わってしまうということも、
双方向データバインディングの場合、
単方向よりも起きやすい気がしています。
これも、あっちこっちにいってしまい、
追うことができなくなってしまうのが原因かと思います。
ただ、このあたりは、保守性が高いコードや設計、厳密なコーディングスタイルなどによって、
ある程度は解消できるものでもある気がするので、やはり正直チームやプロジェクトによると思います。
Reactは、基本的に単方向データバインディングを採用しており、
Reactと比較したVueのデメリットの一つとして、しばしば双方向データバインディングを
挙げられることも多いので、賛否両論の機能であることは間違いなさそうです。
Vue.jsにおいても、v-modelではなく、
v-bindやv-onだけを使えばいいのではないかという意見もありますが、
正直それだと、Vue.jsを採用するメリットが無い気がします。
また、v-modelを使わないとなると、
かなり記述が冗長になることが予想されます。
そのため、双方向データバインディングのデメリットが気になるのであれば、
Vue.jsの採用には慎重になったほうがいいかもしれません。
v-modelだけでなく、Vue.jsには、propsに加えemitというものもあり、
ここも双方向データバインディングのような動きをします。
しかし、逆にこのような双方向データバインディングのほうが、
記法をすっきりさせたり、柔軟な記述ができるという、
メリットの部分を重視すれば、Vue.jsを採用するのも有りだと思います。
まあ、結局は好き嫌いですね(笑)
ちなみに、Vue.jsのpropsとemitについては、
下記の記事で紹介しているので、よければご参考ください。
-
参考【Vue3 props/emit】コンポーネント間通信
コンポーネントとは? コンポーネントとは、そのままの意味としては、部品とか、構成要素とかといったものがあります。 Vue.jsにおけるコンポーネントという概念も、大まかにはそのような意味で、より具体的 ...
続きを見る