![今のところinput[type=”number”]を使わない方が良い理由と代替案 アイキャッチ](https://www.fourier.jp/storage/blog/post-outline/jLh85zUW8RaWiVlX9MATwpSprFDEYZw5.jpg)
はじめに
WebComponents を使った実装の際に、 connectedCallback()
内で querySelector()
がnullになることがあったので、今回はその理由について解説していきます。
理由
さっそくですが結論から、
connectedCallback()
の実行時には、まだDOM解析が完全に終わっていないからです。
解説
例えば <fr-sample-item>
のslot内に、 <fr-sample-item>
を入れた時に動作が変わるように実装したとします。
// <fr-sample-item>
connectedCallback() {
 if (**this.querySelector('fr-sample-item') === null**) {
 this._root.innerHTML = '<a href="" role="listitem">\n' +
 ' <slot></slot>\n' +
 '</a>';
 } else {
 // <fr-sample-item>があったら動作を変える。
 this._root.innerHTML = '<details role="listitem">\n' +
 ' <summary>\n' +
 ' <a href="">\n' +
 ' <slot></slot>\n' +
 ' </a>\n' +
 ' </summary>\n' +
 ' <div role="list">\n' +
 ' <slot name="items"></slot>\n' +
 ' </div>\n' +
 '</details>';
 }
}
実行(Connect)前
<fr-sample>
 <fr-sample-item>ネスト無し</fr-sample-item>
 <fr-sample-item>
 <i class="fa fa-newspaper"></i>
 <fr-sample-item slot="items">ネスト有り</fr-sample-item>
 </fr-sample-item>
</fr-sample>
実行(Connect)後 ※理想
<nav>
 <div role="list">
				<a href="" role="listitem">ネスト無し</a>
				<details role="listitem">
				 <summary>
				 <a href="">
				 <i class="fa fa-newspaper"></i>
				 </a>
				 </summary>
				 <div role="list">
				 <a href="" role="listitem">ネスト有り</a>
				 </div>
				</details>
 </div>
</nav>
実行(Connect)後 ※実際

<nav>
 <div role="list">
 <a href="" role="listitem">ネスト無し</a>
 <a href="" role="listitem"><i class="fa fa-newspaper"></i></a>
 </div>
</nav>
検証環境を用意したので、こちらで動作を確認してみてください。
「ネスト有り」が入った <detail>
が表示されない事が、確認出来るかと思います。
何故このような動作になるのか?
まだDOMが解析されていないため、実行時にslotの中身( innerHTML
等)が空になってしまいます。
例えば、二つ目の <fr-sample-item>
の connectedCallback()
の実行時でのDOM解析状況は以下の通りです。

実行時には、slot内のコードまで解析されていません。 そのため、サンプルコードの this.querySelector('fr-sample-item')
が必ずnullになってしまうのが原因です。
ただし、コードの読み込みをパーサーブロッキングしていない場合は起こりません。(パーサーブロッキングした方が良いです。後述します。)
-
customElements.define()
をDOMContentLoadedイベントで実行させる。 - サンプルコードの読み込みに非同期(async)や遅延(defer)で読ませる。
-
setTimeout()
で実行を遅延させる。
検証環境を用意したので、こちらで動作を確認してみてください。
なお JavaScript の読み込み時にdefer属性を追加している以外に、変更点はありません。

解決方法
ですが、WebComponentsの読み込みはパーサーブロッキングをした方が良いです。
そこでやや面倒ですが、もう一つcustomElementを定義して使えば解決します。
class FrSampleItem extends HTMLElement {
		// 略
 connectedCallback() {
 this._root.innerHTML = '<a href="" role="listitem">\n' +
 ' <slot></slot>\n' +
 '</a>';
 }
}

class FrSampleNestItem extends HTMLElement {
		// 略
 connectedCallback() {
 this._root.innerHTML = '<details role="listitem">\n' +
 ' <summary>\n' +
 ' <a href="">\n' +
 ' <slot></slot>\n' +
 ' </a>\n' +
 ' </summary>\n' +
 ' <div role="list">\n' +
 ' <slot name="items"></slot>\n' +
 ' </div>\n' +
 '</details>';
 }
}

customElements.define('fr-sample-item', FrSampleItem);
customElements.define('fr-sample-nest-item', FrSampleNestItem);
<fr-sample>
 <fr-sample-item>ネスト無し</fr-sample-item>
 <fr-sample-nest-item>
				<i class="fa fa-newspaper"></i>
 <fr-sample-item slot="items">ネスト有り</fr-sample-item>
 </fr-sample-nest-item>
</fr-sample>
まとめ
WebComponents での実装には多くの落とし穴があり、一筋縄では実装が出来ません。
落とし穴を回避出来るような情報を、当ブログでなるべく貢献が出来ればと思います。
参考