Vue Refの糖衣構文と Svelte


10/28に、Vue の著者ユーさんは Vue Ref の糖衣構文に関する RFC を提案した。

refの定義をJavaScriptラベル構文を使うことでさらに簡略化できます。この構文はSvelteとまったく瓜二つなので、ここで私の考えを書く。

まず、コードを見ていきましょう。

<script setup>
// ラベル構文
ref: count = 1

function inc() {
  // .valueせずこのままcountを使える
  count++
}

console.log($count.value)
</script>

<template>
  <button @click="inc">{{ count }}</button>
</template>

このコードを理解するには、まず Vue Composion APIをどんな役割をしているのかを理解しなければならない。

Vue Composition API はいくつのAPIがあり、変数を reactive にすることができます。例えば:

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

Vue Composition API の ref を使って、変数を reactive になる。つまり変数を更新するたび、画面上使っているDOMは自動的に更新されます。

<template>
  <div>{{ count }}</div> // 変数 count が更新されたら DOM も更新されます。
</template>

<script>
  export default {
    setup() {
      return {
        count: ref(0)
      }
    }
  }
</script>

React の場合、ちょっと useState に似たように、setState を呼び出した場合 DOM が更新されます。Vue の強みは setState みたいな関数を使わなくてもいいというところだと思います。count.value++の構文だけで更新されることはとても便利だと思います。

ここで注意しないといけないことがある。ref を使う場合、本当の値を読み込む場合、.valueしないといけないということです。大したことではないが、少しオーバーヘッドドキュメントも記載されている

  • ユーザーは普通の変数か、refの変数か判断する場合がある(まあ、TypeScript使えば大したことではないけど)
  • template で普通に countで使えるのに、コードの中で count.value 使う必要がある。
  • .valueで読み込む必要がある

だから Ref Sugar そういう提案があった:

<script setup>
// ラベルを使って ref を定義する
ref: count = 1

function inc() {
  // 直接 count++ できる(まえは count.value++)
  count++
}
</script>

<template>
  <button @click="inc">{{ count }}</button>
</template>

変換したコードはこうなります

<script setup>
  const count = ref(1);
  function inc() {
    count.value++;
  }

  console.log(count.value)
</script>

<template>
  <button @click="inc">{{ count }}</button>
</template>

この糖衣構文はref定義を隠していると同時に、.valueを使用せず変数を直接アクセスできるため、開発者に負担が軽減されていることがわかります。

ですが、コミュニティはほぼ反対意見だった。

  • これは正しいJavaScript構文ではないこと(ラベル構文自体は合法ですが、同じラベルを定義するのはだめらしい)
  • Vueはラベル構文の意味を勝手に変更した
  • let, const使わず直接変数を定義することが変。
  • コンパイルする必要があって複雑になった。

興味があったらぜひRFCも見てみてください。

著者自身も、このアイデアはSvelteから借用したと述べていますが、Svelteがどのように使っているのかを見てみよう:

<script>
	let name = 'world';
	// name更新するたびにこのコードを実行される
	$: console.log(name);
  // name更新するたびにname2に代入
	$: name2 = name.toUpperCase();
  // マントのときだけ実行
 	$: console.log('');
</script>

$ラベルついてるコードは、Svelteによってこんなふうにコンパイルされる

let name2;
$$self.$$.update = () => {
  if ($$self.$$.dirty & /*name*/ 1) {
    $: console.log(name);
  }

  if ($$self.$$.dirty & /*name*/ 1) {
    $: name2 = name.toUpperCase();
  }
};

$: console.log("");

$$self.$$などまず無視して、updateの関数だけ注視する。updateの関数はコンポネントが更新されたときに実行する。Svelteは変数が更新されているかどうかをまず確認し(if 構文のことろ)、それからコードを実行するかどうかを決定する。

下部のconsole.log()はコンポーネントの変数を使っていないため、コンパイルされても updateに入れないことがわかります。

3つ以上の変数がある場合、Svelteはどの変数が変更されたかを認識し、対応するコードを実行できます。

let name = 'world';
let count = 0;

// name変更されたら実行する
$: console.log(name);
// count変更されたら実行する
$: console.log(count);

ここで、Svelteが依存関係を可能な限り追跡することがわかります。Reactのふうに変換すると次のようになる

const [name, ] = useState('world');
const [count, ] = useState(0);

useEffect(() => {
  console.log(name);
}, [name]);

useEffect(() => {
  console.log(count)
}, [count])

つまりSvelteは、コンパイルを通じて内部的にどの依存関係を持っているかを事前に認識し、それを処理できるため、実行時(ランタイム)に依存関係を明示的に宣言する必要はない。

もちろん、この部分にはメリットとデメリットがあり、トレードオフも含まれている。

構文とコンパイルの力を貸して、コードを大幅にシンプル化でき、簡潔なコードで開発者の負担を軽減できることがわかる。

このため、黒魔術と見なされやすいです。

議論をより集中させるためには、魔法の定義が何であるかを明確にする必要があるので、私は魔法を次のように定義する

  • コードの動作が期待と違う

  • コードと実際にコンパイルされたコードと異なる(ラベルのコードはコンパイル後に完全に異なります)

これらの2つの議論に基づいて、さまざまな議論を導き出すことができます。

  • JSX、テンプレート、SFCがJavaScriptコードに処理されますが、それは魔法ですか?開発者が一般的にこの構文を受け入れるのはなぜですか?

  • v-if v-show v-forなどフレームワークが提供されたテンプレート構文は魔法にカウントされるのか?

  • ReactのonChangeイベントは、ブラウザのonChangeイベントとは異なりますが、標準を再定義したね。それも魔法ですか?(参考

ただし、Vue Ref Sugarだけの観点からは、この糖衣構文がもたらすメリットは実際にはかなり限られています。

効果もSvelteとは異なります。Svelteの$ラベールは、内部依存関係が変更されたときにコードを再実行することですが、Vueのref:はref変数を定義するだけです。構文は似ていますが、効果は完全に異なる。

まとめ

自分は ref 糖衣構文についても反対意見を持っていますが、ラベル構文を使うわりにそんなにいいところはないと思っています。そもそも refreactive 分けて提供されることについて本当にいいことなのか?

逆に React は比較的に重くてバンドルサイズもちょっと大きいですが、APIは少なくドキュメントもちゃんと書いてくれて、同じ目的を達成するためにReactで書くならちょっと増えるかもしれないが、すべてランタイムだから逆に黒魔術そんなになくていいかもしれません。

まあ結局人々の好みによるかもしれないが、自分を選んだものがちゃんと胸を張って好きって言えばいい。他のフレームワークと強引に比べる筋合いはない。つまらない争いをやめて、お互いの良さを勉強し、一緒によいフレームワークを作ればいいと思う。