Nuxt4のマイグレーションを試してみた
先日、Nuxt3から4へのマイグレーションガイドが公開されました。
まだ正式リリースではありませんがガイドに従って設定することでNuxt4を試すことができます。
私も担当しているプロジェクトで、以下の記事を参考に移行を行ってみました。
いくつか利点はありますが、特にfetch周りのパフォーマンスが大幅に改善されたと体感しました。
Vuetifyとの相性?
しかしながら、VueのUIライブラリとしてメジャーなVuetify3と併用するときには注意が必要かもしれません。
実際に私が担当するPJではVDataTableというUIコンポーネントを多用しています。
どれも一般的な用途で、APIでデータを取得し、テーブルで表示するというものです。
問題が起きたのはテーブルの項目をselectするときです。
「やたら重いな・・・?」
明らかにNuxt3の時よりcheckの挙動が遅くなっています。
思いつく限りの原因を潰してみる
自身の書き方がまずいのかもしれない、Vuetifyのバージョンアップで改善されているかもしれないなど、いくつか思いつく方法を試してみました。
- slot内でフォーマット関数を使わない
Vuetify2時代の記事ですが、「VDataTableのslotで関数を使うとSelectのたびに全項目でレンダリングが行われるのでNG」というコメントがありました(こちらは解決
実際にフォーマット関数にログを仕込んでみたところ、selectしていない行でも全件フォーマット関数が実行されていることを確認しました(既にクローズ)
https://github.com/vuetifyjs/vuetify/issues/5986#issuecomment-1205066197
コード値をEnumで文字列に変換したり、YYYY-MM-DD形式の日付をYYYY年MM月DD日にするなど、簡単な処理ではありましたが事前に変換しておくことで再計算を減らすことができました。
しかしまだVDataTableの挙動は重いままでした。
2.検索結果一覧、Select変数をshallowRefで定義する
shallowRefを使うことで、オブジェクトや配列で浅めに定義することでネストした変更の監視をしないため、適切に使うことでパフォーマンスの改善が見込めます。
しかしVDataTableはselectした際にどうやらオブジェクトを再生成してから再代入するようで、selectした時点で全件でのレンダリングが発生していました(この辺り認識曖昧で情報も少)
- #[
item.data-table-select
]でプレーンな要素に置き換える
Vuetifyのコンポーネントは内部的にv-checkboxやv-text-field,v-checkbox-btnなど基本的なコンポーネントを利用していることが多いです。
VDataTableであればshow-selectプロパティをtrueにするとチェックボックスが表示されますが、これはVCheckboxBtnが利用されているようです。
TIP:
A simpler version,
v-checkbox-btn
is used primarily as a lightweight alternative in data-table components to select rows or display inline boolean data.
以前、内部的に利用されていたV-Text-Fieldをinputタグに変更することでパフォーマンスが改善されたことがありました。
それに倣い、今回もinputタグに変更してみました。
...
<template #[`item.data-table-select`]="{item}">
<input type="checkbox" @change="selectRow(item.id)" />
</template>
...
//selectRowは対象の行IDがselect配列に存在する場合は削除、存在しない場合は追加する処理
しかしながらこちらも効果がありませんでした。
4.VDataTableVirtualを使ってみる
レンダリングパフォーマンスの改善は見られたがページング対応していないため要件が合わず。
5.ほかのUIライブラリを試してみる
今回はQuasarを試してみました。
VueのUIライブラリは多くありますが、Vuetifyに次ぐstar数だったこともありこちらのテーブルコンポーネントを試してみました。
6.v-memoを試してみる
v-memoとは、変数の変化、特定の条件でテンプレートをメモ化するディレクティブです。
selectしている行以外のレンダリングを止めるためにslotを各セルごとではなく行全体で書くように修正し、選択している行以外のレンダリングを行わないようにできないかと試してみました。
しかしながら1行目と同じ内容が繰り返しレンダリングされてしまうようになってしまい断念しました(公式サンプルではv-forの中では機能しないという記載があるので、使い方が間違っているのかもしれないがVDataTableでどう書くのが適切かわからなかった)
最終的にどうなったのか
結果として5のUIライブラリを変えたことでパフォーマンスは大幅に改善しました。
Vuetifyは非常に素晴らしいライブラリなのですが、稀にパフォーマンスの面で問題が生じることがあるためピンポイントで他UIライブラリのコンポーネントを試したり、場合によってはフルスクラッチするのもありかもしれません。
QuasarのTableではVDataTableで設定していたプロパティとほぼ同じことができているので様子を見ながら置き換えていこうと思います。
shallowRefはテーブル構造等複雑なオブジェクトの一部のプロパティを書き換える際の不要な再レンダリングなど監視をゆるめたい場面で有効ですが、今回のケースではコンポーネント自体の仕様もありうまく活用できませんでした。
Vueの内部的な仕組みについてより勉強していく必要があると感じた課題でした。