本セッションの登壇者
セッション動画
スライド
よろしくお願いします。kazuponといいます。Vue.jsのCore Team Memberをしており、Vue.js日本ユーザーグループの代表をしています。

本題に入る前に少し宣伝させてください。今年Vue Fes Japan Online 2022を開催します。チケットも現在販売中ですので、ぜひみなさんご参加ください。今回はTechFeedさんもスポンサーに入っていただいています。

Reactivity Transformとは
さっそく本題に入っていきます。本日はReactivity Transformについてお話しますが、これはまだ実験的な機能で今後変わる可能性があるので、使う方はちょっと注意して使ってください。

Reactivity Transformは、コンパイラマクロを使ってComposition APIのリアクティブ周りの問題を解決するためのもので、位置付けとしてはComposition APIのDXを改善するために生まれたものです。

提供されるコンパイラマクロは$
でプリフィックスされていて、Composition APIのリアクティビティ関連APIに対応するマクロや、リアクティビティ周りの振る舞いを取り扱うためのマクロが用意されています。

これらのマクロは実体をもっておらず、TypeScriptで型として定義されています。技術的には、コンパイラマクロがビルドされて動作するようになっています。実際にはBabelを使って、マクロで書かれたVueのコードのASTレベルで解析して、ビルド時に動くJSコードにトランスフォームしています。

Reactivity Transformが解決する問題
Reactivity Trasnformが具体的にどのような問題を解決するのか、主に3つについて話します。
まず1つ目は、reactive
とref
の取り扱いが難しいという点です。Composition APIではreactive
とref
を使って値をreactiveにすることができます。reactive
関数はオブジェクトをreactiveにできますが、number
やstring
などのプリミティブ値は処理できません。こちらのコードのように、オブジェクトをreactiveにできるのですごく便利です。

それに対して、ref
はプリミティブな値を処理でき、オブジェクトも取り扱うことができます。なので、Composition APIを使うときreactive
とref
の区別が面倒くさいという話がよくあるのですが、ref
に統一して処理することは可能です。ただし、ref
を使うと.value
でアクセスしなければいけないという欠点があります。TypeScriptを使っていると型がきくので問題ないのですが、デザイナーさんがJavaScriptでコードを書くときやVSCodeを使っていない場合など、型がチェックされない環境では.value
での値アクセスでミスが起きやすいという問題があります。

2つ目は、reactiveな値を分割代入で取り扱うのが難しいという点です。具体的には下記のコードのようにマウスポインタの座標位置をreactiveに取り扱うcomposableな関数useMouse
があるとします。この関数ではreactive
でx, y座標をstate
に返しますが、ここで分割代入を使ってしまうとreactivityが失われてしまいます。watchEffect
でconsole.log('update', x, y)
と書いて、マウスを動かして座標を更新しても、表示は変わりません。

Propsにも同様の問題があり、<script setup>
内でdefineProps
に渡したPropsに対して分割代入を使うと、これもreactivityが失われてしまいます。

3つ目は、Propsのデフォルト値の定義方法に関する問題です。<script setup>
の登場以降、defineProps
を使ってPropsを定義できるようになったのですが、デフォルト値はwithDefaults
マクロを使わないと定義できません。

Reactive Transformを使うとどうなるか
Reactive Transformを使うとこれらの問題がどう解決できるかをご説明します。
まず1つ目の「ref
とreactive
の取り扱いが難しい」という問題については、値はほぼref
を使って対応できるようになります。こちらのコードのように、コンパイラマクロ$ref
を使うと.value
で値にアクセスすることなくコードを書くことができます。コンパイルされると.value
で値にアクセスするコードに変換されます。

2つ目の「値を分割代入で取り扱うのが難しい」という問題については、たとえば先ほどのuseMouse
関数(マウスポインタの座標をreactiveなstateとして返す関数)であれば、useMouse()
を呼び出すときに、$()
マクロでラップすると分割代入が使えるようになります。$()
マクロでラップすると、コンパイル時に分割代入のコードは変換され、toRef
で戻り値がref
・ref
以外のどちらでも対応できるように変換されます。

Propsも同様で、<script setup>
でのdefineProps
が改善され、分割代入を使った場合もコンパイル後にProps経由でアクセスできるようになります。

3つ目の「Propsのデフォルト値の定義方法」については、JavaScriptのデフォルト値定義の構文で定義できるようになり、エイリアスも使えるようになります。

ただし注意点があります。
1点目は変数の定義でlet
とconst
を使い分けなければいけないということです。下記のコードではcount
を定義後に書き換えるので、定義時にはlet
を使わなければなりません。$computed
で定義し、参照するだけの場合はconst
で定義できます。

また、マクロで定義した変数をreactiveな関数に渡すとき、とくにref
を引数として要求された場合は、$$()
マクロでエスケープしないとreactiveにならないという罠があるので注意が必要です。

Composableな関数でreturn
するときにも$$()
でエスケープしないとreactiveにならないため、注意が必要です。

Reactivity Transformをサポートするツール
Reactivity Transformをサポートするツールももちろん提供される予定です。
Vue.js用のVSCode拡張のVolarと、ESLintのプラグインも現時点では開発されていませんが今後開発される予定です。バンドラについては現時点でオプトインして使えるようになっています。

以上です。ありがとうございました。