logo hsb.horse
← スニペット一覧に戻る

Snippets

dataset で冪等な DOM オブザーバーのセットアップ

data属性を使ってMutationObserverの重複登録を防ぐシンプルなパターン。グローバルレジストリ不要で移植性が高い。

公開日: 更新日:

MutationObserver を複数回セットアップすると、同じ要素に複数のオブザーバーが重複して登録される。グローバルな Set や Map で管理する方法もあるが、dataset に観測済みフラグを書き込む方法は軽量で移植しやすい。

コード

const OBSERVE_ID_KEY = 'observeId'
const isElementObserved = <T extends HTMLElement = HTMLElement>(
ele: T
) => Object.hasOwn(ele.dataset, OBSERVE_ID_KEY)
const setObserverId =
(id: string) =>
<T extends HTMLElement = HTMLElement>(ele: T): T => {
ele.dataset[OBSERVE_ID_KEY] = id
return ele
}
export const observeElement = (
observeId: string,
element: HTMLElement | string,
observerCallback: MutationCallback,
options: MutationObserverInit = { childList: true }
) => {
const observedElement =
element instanceof HTMLElement
? element
: document.querySelector<HTMLElement>(element)
if (observedElement && !isElementObserved(observedElement)) {
const observer = new MutationObserver(observerCallback)
observer.observe(observedElement, options)
setObserverId(observeId)(observedElement)
return observer
}
}

使用例

// セレクター文字列でも HTMLElement でも渡せる
observeElement(
'my-list-observer',
'#item-list',
(mutations) => {
for (const mutation of mutations) {
console.log('変更を検知:', mutation.type)
}
}
)
// 同じ要素に再度呼んでも重複登録されない
observeElement('my-list-observer', '#item-list', callback)

仕組み

  1. Object.hasOwn(ele.dataset, OBSERVE_ID_KEY)data-observe-id 属性の存在を確認する
  2. 未登録の場合のみ MutationObserver を生成して observe() を呼ぶ
  3. 観測開始後に ele.dataset[OBSERVE_ID_KEY] = id でフラグを書き込む
  4. 次回以降の呼び出しでは isElementObservedtrue を返すため何もしない

dataset の値は HTML の data-* 属性に直接書き込まれるため、DevTools で確認できる。

メリット

  • グローバルレジストリ不要:状態を要素自身に持たせるため、Map や Set を別途管理しなくてよい
  • 移植性が高い:フレームワークへの依存がなく、あらゆる環境で動く
  • デバッグしやすい:属性値を DevTools で直接確認できる

注意点

要素が DOM から削除されると dataset の情報も消える。同じ要素が再挿入された場合は再びオブザーバーが登録される点に注意すること。また、オブザーバーを止めたい場合は observer.disconnect() を別途呼ぶ必要がある。

実務メモ

このスニペットは、TypeScript、JavaScript、DOM、MutationObserver の周辺で同じ操作や判定を毎回書きたくない時に向く。小さな補助として切り出しておくと、呼び出し側では意図だけを追いやすい。

逆に、分岐や前提条件が増えて責務が膨らむなら、1本のスニペットに詰め込まない方がよい。手順と helper を分けるか、役割ごとに切り出す方が保守しやすい。