`<wa-select>` と `<wa-combobox>` の大量オプション時のパフォーマンス改善
<wa-select>、<wa-combobox>、<wa-option> の3コンポーネントに対して、スロット変更のバッチ処理、オプションのキャッシュ、チェックアイコンの遅延レンダリングを導入し、大量オプション時のパフォーマンスを改善しました。
背景
大量の <wa-option> を持つ <wa-select> や <wa-combobox> で、パフォーマンス上の問題が報告されていました。この問題は discussions/2197 で議論されており、オプションが追加・変更されるたびにラベル再計算やオプション一覧の再収集が同期的に実行される設計が原因でした。
従来の実装では、<wa-option> の connectedCallback でラベルの初期化を即時実行し、スロット内容が変わるたびに updateDefaultLabel() を同期呼び出していました。また <wa-select> はスロット変更のたびにすべてのオプションを再収集していたため、オプション数が増えるほどコストが線形に積み重なる構造でした。
これらの問題を、キャッシュ・遅延評価・バッチ処理という3つのアプローチで解消したのが本PRです。
技術的な変更
<wa-option>: ダーティフラグによる遅延ラベル計算
defaultLabel をリアクティブな @state() プロパティからゲッターに変換し、ダーティフラグ (isDefaultLabelDirty) で再計算のタイミングを制御するようになりました。
変更前:
@state() defaultLabel = '';
connectedCallback() {
super.connectedCallback();
// ...
this.updateDefaultLabel();
}
private handleDefaultSlotChange() {
// Tell the controller to update the label
this.updateDefaultLabel();
// ...
}
変更後:
private cachedDefaultLabel = '';
private isDefaultLabelDirty = true;
get defaultLabel(): string {
if (this.isDefaultLabelDirty || !this.cachedDefaultLabel) {
this.updateDefaultLabel();
}
return this.cachedDefaultLabel;
}
private handleDefaultSlotChange() {
// Mark the default label as needing recalculation
this.isDefaultLabelDirty = true;
// ...
}
connectedCallback での即時 updateDefaultLabel() 呼び出しも削除されました。スロット内容が変化した際も即座に再計算するのではなく、isDefaultLabelDirty = true とマークするだけになり、実際の計算は defaultLabel が読み取られるまで遅延されます。
<wa-select>: オプションキャッシュとスロット変更のバッチ処理
<wa-select> には cachedOptions と slotChangePending の2つのフィールドが追加されました。
private cachedOptions: WaOption[] | null = null;
private slotChangePending = false;
getAllOptions() の結果を cachedOptions にキャッシュすることで、スロット変更がない限り同一の配列を返し、オプション一覧の再収集コストを削減しています。slotChangePending フラグはスロット変更イベントをバッチ処理するために使われており、複数のオプションが短時間に追加された場合でも processSlotChange() の実行を1回にまとめます。
また optionValues の再構築ロジックも最適化されました。従来は毎回 value == null を判定して全オプションを走査していましたが、変更後は optionValues === undefined のときのみ再構築し、キャッシュが有効な間は再走査をスキップします。
初期化処理も変更されており、connectedCallback では handleDefaultSlotChange の代わりに processSlotChange を直接呼び出すようになりました。コード中のコメントによれば、初回のセットアップを同期的に実行するためであり、その後のオプション追加は handleDefaultSlotChange 経由でバッチ処理されます。
設計判断
遅延評価とダーティフラグの組み合わせ が採用された点が設計上の核心です。defaultLabel をゲッターに変換することで、値が必要になるまで計算を先送りにできます。これは特に、オプションが画面外にある場合や、ドロップダウンが開いていない場合に無駄な計算を避ける効果があります。
バッチ処理 の導入は、Webコンポーネントにおける典型的な最適化パターンです。DOMへのオプション追加は複数回まとめて行われることが多く、各追加に対して即時処理するよりも、一定期間の変更を集約してから処理する方がコスト効率が高くなります。slotChangePending フラグによるデバウンス的なアプローチがこの目的を果たしています。
これらの変更はすべて内部実装の変更であり、コンポーネントの公開APIは変わりません。既存の利用コードへの影響なく、パフォーマンス特性のみが改善されます。
まとめ
本PRは、ラベル計算の遅延評価・オプション一覧のキャッシュ・スロット変更のバッチ処理という3つの最適化をそれぞれのコンポーネントに適切に配置することで、大量オプション時のパフォーマンスボトルネックを解消しています。公開APIを変えずに内部実装のみを最適化する手法は、コンポーネントライブラリの後方互換性を保ちながら性能を向上させる堅実なアプローチといえます。