Tech blog Produced by FOURIER

connectedCallback()内でquerySelector()がnullになる理由

Sena Sena 2022.11.02

はじめに

WebComponentsを使った実装の際に、connectedCallback() 内でquerySelector() がnullになることがあったので、今回はその理由について解説していきます。

理由

さっそくですが結論から、

connectedCallback()の実行時には、まだDOM解析が完全に終わっていないからです。

解説

例えば  のslot内に、 を入れた時に動作が変わるように実装したとします。

// <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>

検証環境を用意したので、こちらで動作を確認してみてください。

「ネスト有り」が入った  が表示されない事が、確認出来るかと思います。

検証環境(slotが空)

何故このような動作になるのか?

まだDOMが解析されていないため、実行時にslotの中身(innerHTML等)が空になってしまいます。

例えば、二つ目の  の connectedCallback() の実行時でのDOM解析状況は以下の通りです。

実行時には、slot内のコードまで解析されていません。 そのため、サンプルコードのthis.querySelector('fr-sample-item')が必ずnullになってしまうのが原因です。

ただし、コードの読み込みをパーサーブロッキングしていない場合は起こりません。(パーサーブロッキングした方が良いです。後述します。)

💡
パーサーブロッキングしない例
  • customElements.define()をDOMContentLoadedイベントで実行させる。
  • サンプルコードの読み込みに非同期(async)や遅延(defer)で読ませる。
  • setTimeout()で実行を遅延させる。
  • 検証環境を用意したので、こちらで動作を確認してみてください。

    検証環境(defer属性付与)

    なお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での実装には多くの落とし穴があり、一筋縄では実装が出来ません。

    落とし穴を回避出来るような情報を、当ブログでなるべく貢献が出来ればと思います。

    参考

    https://stackoverflow.com/questions/63072769/how-to-use-queryselector-of-tags-inside-a-web-component

    Sena

    Sena / Engineer

    生涯に亘り技術を極めていきたい。