はじめに
ReactユーザーがVueの欠点として必ず挙げるもののひとつに 双方向データバインディング があります。
彼らの主張によるとVueでは双方向データバインディングができてしまうことでデータの更新がカオスになってしまうと言うのです。
では何故実際にVueでデータの更新がカオスになって困ったという記事を全く見ないのでしょうか?
VueユーザーがReactユーザーに比べて優れているからでしょうか?
いいえ、違います。
そもそもVueの双方向データバインディングはただのシンタックスシュガーであり、本質はReactと同じ単方向データバインディングだからです。
なぜ双方向データバインディングがダメと言われるようになったのか
その昔、フロントエンドのフレームワークはAngular一強という時代がありました。
当時、Angularは双方向データバインディングを採用したイケてるフレームワークとして多くのプロジェクトで使われていました。
しかし、アプリが複雑化していく中でAngularの双方向データバインディングの問題点を指摘する声があがりはじめます。
それはまさに冒頭で書いたデータの更新がカオスになってしまうというものでした。
しかし、これは厳密には双方向データバインディングによる問題ではありません。
問題の本質は、値の所有者である親が、子からの変更を受け入れるかどうかを決められなかったことにあります。
コンポーネントが受け取る値を双方向にするかの設定は受け取る側にありました。
そのため子に渡した全ての値は子孫に更新される可能性があり、どこで更新されたのかはその値を受け取った全ての子孫をたどる必要がありました。
この、どの値がどこで更新されているのか特定しにくいということが過剰に問題視され、双方向データバインディングはダメだという意見が広まったように記憶しています。
ただ、Webアプリでは基本的になんらかの操作を起点に処理が実行されるので、実際にはそんな手に負えないようなことにはなったことはありません。
とりあえず双方向データバインディングをダメということがかっこいいみたいな風潮もあったように思います。
ReactとVueで値の更新の流れの違い
これを説明するために、inputタグからの入力をpタグに表示する簡単な例をReact、Vueでそれぞれ実装してみます。
まず、単方向データバインディングといわれるReactでの例を見てみましょう。
import { useState } from 'react';
function Example() {
const [value, setValue] = useState("");
function handleInput(e) {
setValue(e.target.value);
}
return (
<div>
<input value={value} onChange={handleInput} />
<p>{value}</p>
</div>
);
}
次に、Vueの例です。
<script setup lang="ts">
import { useState } from "./use-state";
const [value, setValue] = useState("");
function handleInput(e) {
setValue(e.target.value);
}
</script>
<template>
<div>
<input :value="value" @input="handleInput" />
<p>{{ value }}</p>
</div>
</template>
なんということでしょう。それぞれの書き方の違いはあるもののほとんど同じです。
実際にはReactに寄せるためだけに以下のコンポーザブルを実装していますが、 value属性
で値を渡し、 input
イベントで値を更新するという流れは全く同じです。
(ReactのonChanageはinputイベントでも発火します)
import { ref } from 'vue'
export function useState(initialValue: string) {
const value = ref(initialValue);
const setValue = (newValue: string) => {
value.value = newValue;
}
return [value, setValue] as const;
}
Vueの双方向データバインディングとは
冒頭にも書きましたが、Vueの双方向データバインディングは単なるシンタックスシュガーです。
つまり、 v-model
を使うことで上で書いた例を下記のように簡単に記述することができるというだけなのです。
<script setup lang="ts">
import { ref } from "vue";
const value = ref("");
</script>
<template>
<div>
<input v-model="value" />
<p>{{ value }}</p>
</div>
</template>
まとめ
双方向データバインディング自体に問題はありません。安心して使いましょう。