SSRにおける改善
バージョン3.4と3.5で、SSRのハイドレーションに関する幾つかの問題点も改善されています。本節では、それらを紹介していきます。なお、SSRに関しては、Vue単体での実現は難しく、Nuxtを利用するのが現実的です。そのため、本節の内容もNuxtを基本として解説します。
ハイドレーションミスマッチに関する改善
Nuxtの場合は、単純なSSRではなく、ユニバーサルレンダリング(SSRとCSRを組み合わせたレンダリング)を基本としています。その場合、サーバサイドでのレンダリングとクライアントサイドでのレンダリングに違いがでてしまう問題、つまり、ハイドレーションミスマッチの問題が常につきまといます。その際のエラーメッセージが、バージョン3.4でわかりやすいものへと変更されました(図4)。
図4の例は、リスト8のようなテンプレートブロック記述のコンポーネントを表示させた場合の例です。リスト8の(2)で表示させている変数randは、スクリプトブロックで生成した乱数です。ということは、コンポーネントがレンダリングされるたびに(2)の表示内容は変わります。ユニバーサルレンダリングの場合、サーバ側でレンダリングした内容とクライアント側でレンダリングした内容が変わってきます。図4中に確認できる「rendered on server」と「expected on client」というメッセージは、まさにハイドレーションミスマッチを具体的に表した内容となっています。
<section>
<h4>乱数の表示</h4> // (1)
<p>生成した乱数は{{rand}}</p> // (2)
</section>
このハイドレーションミスマッチに関して、今回の乱数表示のように、あらかじめミスマッチが起こりうることが想定されており、そもそもエラーメッセージの表示が不要な場合もあります。そのような場合に記述する属性として、data-allow-mismatchがバージョン3.5で導入されました。例えば、リスト8の(2)に対して、次のコードを記述すると、図4のエラーメッセージは表示されなくなります。
<p data-allow-mismatch>生成した乱数は{{rand}}</p>
一意のid属性値を生成するuseId()
例えば、リスト8のコンポーネントひとつの画面に複数埋め込むことを考えます。その際に、(1)のh4タグと(2)のpタグにid属性を付与したいとすると、レンダリングされた画面全体においてユニークな値を付与できる仕組みが必要となります。これを実現できる関数として、useId()がバージョン3.5で導入されました。
これを利用して、例えば、リスト9のようなRandSectionコンポーネントを定義したとします。(1)と(2)のように、useId()で取得した値を、(3)と(4)のようにid属性としています。
<script setup lang="ts">
:
const idH4 = useId(); // (1)
const idP = useId(); // (2)
</script>
<template>
<section>
<h4 v-bind:id="idH4">乱数の表示</h4> // (3)
<p v-bind:id="idP" data-allow-mismatch>生成した乱数は{{rand}}</p> // (4)
</section>
</template>
このRandSectionを2個埋め込んだ画面をレンダリングした場合、リスト10の通りになります。適切にユニークなid属性値が割り当てられているのがわかります。
<section> <h4 id="v-0">乱数の表示</h4> <p id="v-1">生成した乱数は46</p> </section> <section> <h4 id="v-2">乱数の表示</h4> <p id="v-3">生成した乱数は49</p> </section>
非同期コンポーネントとハイドレーションの改善
SSRに関連して、バージョン3.5で導入された仕組みとして、非同期コンポーネントのハイドレーションのタイミングが選択可能になったことがあります。すなわち、クライアント側でいつコンポーネントがレンダリングされるかを選択できるようになったのです。これを、Lazy Hydrationと呼んでいます。例えば、リスト11のようなコードです。
const AsyncRandSection = defineAsyncComponent({
loader: () => {
return import("@/components/RandSection.vue");
},
hydrate: hydrateOnVisible() // (1)
});
Lazy Hydrationを実現する場合は、非同期コンポーネントを定義するdefineAsyncComponent()の引数オブジェクトに、hydrateプロパティを定義することです。このhydrateプロパティの値としては、表1のものが可能です(これ以外に自作も可能)。
|
値 |
ハイドレーションのタイミング |
|---|---|
| hydrateOnIdle() | ブラウザがアイドル状態の時 |
| hydrateOnVisible() | コンポーネントの要素部分が表示された時 |
| hydrateOnMediaQuery() | メディアクエリと一致した時 |
| hydrateOnInteraction() | イベントがトリガーされた時 |
リスト11では、hydrateOnVisible()を指定しているため、ブラウザをスクロールするなどして、AsyncRandSection部分が表示されるようになった際に、クライアントでレンダリングが実行されるようになります。
カスタム要素の改善
最後に、カスタム要素に関する改善を簡単に紹介しておきます。カスタム要素は、Vueプロジェクトのみを対象としている場合は、ほとんど不要なものと言えます。一方で、Vueのコンポーネントを、例えばReactなどの他のフレームワークやウェブコンポーネントなどと一緒に使いたい場合に、利用されます。その際利用される関数が、defineCustomElement()です。
このdefineCustomElement()関数のオプションを指定する第2引数の内容が改善され、これまでのstylesプロパティに加えて、バージョン3.5から表2のプロパティが追加されています。
| プロパティ | 内容 |
|---|---|
| configureApp | Vueアプリのインスタンスを設定できる関数 |
| shadowRoot | falseを指定することでShadow DOM(※1)を利用せずにカスタム要素が作成できる |
| nonce | styleタグにnonce属性(※2)として追加される |
※1:ウェブコンポーネントの仕組みのひとつで、ある要素配下のDOMを、その外部のCSSやJavaScriptの干渉を受けないように隠蔽する方法です。隠蔽するDOMの大元の要素をShadow Host、配下の要素をShadow Treeと言います。
※2:Webページ上に埋め込まれた悪意あるコードが実行されないように付与されたランダムな文字列。HTTPレスポンスヘッダに埋め込まれた値と同じ場合に、正規のコードとみなされます。
例えば、リスト12のようなカスタム要素を設定すると、vue-rand-section要素はShadow DOMを利用しないコンポーネントとなります。そのため、そのコンポーネントには外部のCSSが適用されるようになります。
const randSection = defineCustomElement(
RandSection,
{shadowRoot: false}
);
customElements.define("vue-rand-section", randSection);
さらに、カスタム要素から親ホスト要素にアクセスするための表3のヘルパー関数もバージョン3.5で追加されています。
| 関数 | 内容 |
|---|---|
| useHost() | カスタム要素のhostを取得する |
| useShadowRoot() | カスタム要素のshadowRootを取得する |
| this.$host | Options APIでカスタム要素のホスト要素を公開する |
終わりに
Vueのバージョン3.4と3.5の新機能を紹介する本記事は、いかがでしたでしょうか。今後もVueのアップデートに注目していきたいと思います。
