`wa-select` / `wa-combobox` の初期値が表示されないバグを修正
オプションがDOMに追加される前にプロパティ経由で値を設定した場合に選択値がUIに反映されなかった問題が解消されます。value セッターの変更検知ロジックを修正することで、DOMの準備状態に依存しない堅牢な初期値表示を実現しました。
背景
<wa-select> に value プロパティでページロード時の初期値を設定しても、UIに選択値が表示されないバグが報告されました(#2253)。値はコード上は正しく設定されているにもかかわらず、UIには反映されないという挙動でした。
Issue の報告では、コンポーネントのプロパティを設定してから子要素をDOMに追加するというライフサイクルパターンで問題が発生することが示されています。つまり、el.value = 'option-2' を実行した時点では <wa-option> がまだDOMに存在しない状態となり、初期値が正しく表示されません。なお、Issue によれば multiple 属性付きの <wa-select> では同現象は確認されていませんでした。
技術的な変更
select.ts の value セッターにおける変更検知ロジックが修正されました。問題の核心は、optionValues(有効なオプションの値を保持するキャッシュ)が空の状態で value プロパティが設定された際に、変更として検知されなかったことにあります。
変更前のセッターは、変更検知に value ゲッターが返す値を比較していました。value ゲッターは optionValues を通じてフィルタリングを行うため、オプションがDOMに存在しない状態では optionValues が空となり、設定した値が「無効」と判断されて変更なしと見なされていました。
変更後は、ゲッターを介さずに内部のrawな値を直接比較するよう修正されています。新たに追加された rawValuesEqual メソッドが配列の順序まで含めた厳密な等価比較を担い、セッター内の変更検知はこのメソッドを利用するよう変更されました。
private rawValuesEqual(a: string[] | null | undefined, b: string[] | null | undefined): boolean {
if (a == null && b == null) return true;
if (a == null || b == null) return false;
if (a.length !== b.length) return false;
return a.every((v, i) => v === b[i]);
}
セッター内の比較部分は以下のように変更されています。
// 変更前
let newValue = this.value;
if (newValue !== oldValue) {
this.valueHasChanged = true;
this.requestUpdate('value', oldValue);
}
// 変更後
const oldRawValue = this._value;
this._value = val ?? null;
// Compare raw internal values to detect actual changes. We can't rely on the getter because it filters through
// optionValues, which may be empty when options aren't in the DOM yet (common with frameworks that set properties
// before appending children).
if (!this.rawValuesEqual(oldRawValue, this._value)) {
this.valueHasChanged = true;
this.requestUpdate('value', oldValue);
}
また、optionValues プロパティから @state() デコレータが除去され、/** @internal */ JSDocが付与されました。これにより、optionValues の変更が不要なリアクティブな再レンダリングを引き起こさないようになっています。テストでは、値を先に設定してからオプションをDOMに追加するシナリオが追加されており、シングル選択・複数選択の両ケースがカバーされています。
設計判断
rawな内部値(_value)を比較する方式 が採用されました。
コード内コメントでも明示されているように、value ゲッターを通じた比較は optionValues によるフィルタリングに依存するため、オプションが未追加の状態では正確な変更検知ができません。内部状態である _value を直接比較することで、DOMの準備状態に依存しない変更検知を実現しています。等価比較ロジックを rawValuesEqual メソッドとして切り出すことで、変更の意図が明確になっています。
optionValues の @state() 除去も同じ思想の延長です。このキャッシュはUIレンダリングとは独立した内部最適化であり、リアクティブな更新トリガーとして機能すべきではありませんでした。/** @internal */ タグを付与することでパブリックAPIではないことを明示しつつ、不要な再レンダリングを抑制しています。
まとめ
本修正は、「プロパティ設定 → 子要素追加」というライフサイクルパターンに起因する変更検知の不具合を、最小限のコード変更で解消しています。value ゲッターとrawな内部値(_value)の責務を明確に分離したことで、オプションのDOM状態に依存しない堅牢な変更検知が実現されました。