JavaScript | DOM 操作:属性操作 – data-* 属性

JavaScript JavaScript
スポンサーリンク

data-* 属性とは何か

data-* 属性は、HTML 要素に「任意のメタ情報」を文字列として安全に持たせるための仕組みです。例として data-id、data-role、data-payload など、好きな名前で付けられます。ここが重要です:data-* は“文字列専用”です。数値や真偽値、配列やオブジェクトを扱いたい場合は、意図した文字列へ変換(例:JSON)してから入れ、取り出した後にパースします。


dataset(DOM からの読み書き)と名前のルール

dataset 経由で簡単に扱う(kebab → camelCase に変換)

<button id="buy" data-sku="12345" data-item-name="Apple">購入</button>
<script>
  const btn = document.getElementById("buy");
  console.log(btn.dataset.sku);      // "12345"(data-sku)
  console.log(btn.dataset.itemName); // "Apple"(data-item-name → itemName)
  
  // 書き込むと属性が増える/変わる
  btn.dataset.itemName = "Banana";   // data-item-name="Banana" に更新
</script>
HTML

dataset は data-xxx を“camelCase のプロパティ名”として扱います。ここが重要です:data-user-id → dataset.userId、data-user2-id → dataset.user2Id のように、ハイフンは消えて次の文字が大文字になります。属性名の規則を守ると、JS での扱いが直感的になります。

getAttribute / setAttribute で“生の文字列”を扱う

const raw = btn.getAttribute("data-sku"); // "12345"
btn.setAttribute("data-sku", "98765");    // 文字列として直接設定
JavaScript

ここが重要です:dataset は便利ですが、値はすべて文字列。未設定は undefined(dataset)または null(getAttribute)で返る違いがあるため、プロジェクトでどちらを標準にするか決めておくと混乱しません。


型の扱い(数値・真偽・複合データ)

数値や真偽値は明示的に変換する

<div id="card" data-count="0" data-enabled="true"></div>
<script>
  const card = document.getElementById("card");
  const count = Number(card.dataset.count);          // 0(文字列 → 数値)
  const enabled = card.dataset.enabled === "true";   // true(文字列 → 真偽)
HTML

ここが重要です:data-* は必ず文字列。Number(…) や === “true” の判定で“意図した型”に変換します。空文字や未設定に備え、既定値(fallback)を用意すると安全です。

複合データは JSON.stringify/parse のペアで

<button id="item" data-payload='{"id":42,"tags":["new","sale"]}'>購入</button>
<script>
  const raw = document.getElementById("item").dataset.payload; // 文字列
  const payload = JSON.parse(raw ?? "{}");                     // オブジェクトへ
  console.log(payload.tags[0]); // "new"
</script>
HTML

ここが重要です:配列/オブジェクトをそのまま dataset に入れることはできません。JSON で“文字列化して保存→取り出してパース”を徹底します。未設定(undefined/null)で JSON.parse を投げないよう、”{}” や “[]” にフォールバックするのが定石です。


命名と設計(意味が伝わる data-* にする)

接頭辞で範囲を示す(衝突回避)

<div class="card" data-card-id="7" data-card-type="product"></div>
HTML

ここが重要です:コンポーネント名を接頭辞にすると、data-id のような“汎用名”との衝突を防げます。ページ全体でも、どの要素のデータかが一目で分かります。

状態は is-/has-/type-*、値は名詞で

<button data-is-active="true" data-type="primary" data-user-id="123"></button>
HTML

ここが重要です:is- は真偽の状態、type- は種類、*-id は識別子という規則で“見ただけで何か分かる”命名にします。JS 側の判定・変換もシンプルになります。


安全性(文字列として扱う・DOM API を使う)

インジェクションを避ける基本方針

el.setAttribute("data-note", userInput); // 文字列として属性に入る(HTMLは展開されない)
title.textContent = userInput;          // 表示は textContent
JavaScript

ここが重要です:data-* や textContent は“文字”扱い。innerHTML と文字列結合で DOM を作らない限り、意図せぬスクリプト実行は起きません。外部入力は常に文字として扱い、構造はテンプレートや createElement で作ります。


実務パターン(イベント委譲・関連要素参照・動的リンク)

クリック元のデータで処理を切り替える(イベント委譲)

<ul id="list">
  <li><button class="buy" data-sku="1001">購入</button></li>
  <li><button class="buy" data-sku="1002">購入</button></li>
</ul>
<script>
  const list = document.getElementById("list");
  list.addEventListener("click", (e) => {
    const t = e.target;
    if (!(t instanceof Element)) return;
    const btn = t.closest("button.buy");
    if (!btn) return;
    const sku = btn.dataset.sku; // クリックしたボタンの SKU
    console.log("購入:", sku);
  });
</script>
HTML

ここが重要です:イベント委譲と data-* は抜群に相性が良く、後から追加された要素でも同じ処理を共有できます。

参照関係を data-* で表現して辿る

<button id="open" data-target-id="panel">開く</button>
<div id="panel" class="modal is-hidden">内容</div>
<script>
  const open = document.getElementById("open");
  open.addEventListener("click", () => {
    const target = document.getElementById(open.dataset.targetId);
    target.classList.toggle("is-hidden", false);
  });
</script>
HTML

ここが重要です:data-target-id のように“関連要素の id”を持たせると、JS のセレクタを分散せず、依存関係が明確になります。

動的リンク生成(URLSearchParams と併用)

<a id="detail" data-id="42" data-q="赤い 果物">詳細</a>
<script>
  const a = document.getElementById("detail");
  const params = new URLSearchParams({ id: a.dataset.id, q: a.dataset.q });
  a.setAttribute("href", `/items?${params}`); // 正しくエンコードされた URL
</script>
HTML

ここが重要です:ユーザー入力を混ぜるときは自前結合を避け、URLSearchParams に任せる。data-* は“材料”として持ち、最終的な文字列化は安全な API で行います。


取り扱いの落とし穴(未設定・空文字・型変換・タイミング)

未設定と空文字の区別を決める

<input id="age" data-min="">
<script>
  const age = document.getElementById("age");
  // dataset.min は ""(空文字)。未設定なら undefined になることも
  const hasMin = age.hasAttribute("data-min"); // true(存在)
  const min = age.dataset.min;                 // ""
HTML

ここが重要です:空文字を“設定済み(0 や空)”とみなすか、“未設定”とみなすかをプロジェクトで統一します。存在判定(hasAttribute)と中身判定(非空チェック)を分けると誤判定が減ります。

JSON.parse の例外に注意

const raw = el.dataset.payload;  // 例: undefined の可能性
const payload = raw ? JSON.parse(raw) : {}; // 未設定時のフォールバック
JavaScript

ここが重要です:null/undefined をそのまま JSON.parse すると例外になります。常にフォールバックを用意します。

DOM 構築前は読めない(タイミング)

DOMContentLoaded 後、または defer スクリプトで実行します。ここが重要です:必ず要素の存在を確認し、null ガードを入れるクセをつけます。


ユーティリティで標準化(型変換とフォールバック)

function readData(el, name, fallback = "") {
  const v = el?.dataset?.[name];
  return v == null ? fallback : v;
}
function readBool(el, name, fallback = false) {
  const v = el?.dataset?.[name];
  return v == null ? fallback : v === "true";
}
function readNum(el, name, fallback = 0) {
  const v = el?.dataset?.[name];
  const n = Number(v);
  return Number.isFinite(n) ? n : fallback;
}
function readJSON(el, name, fallback = {}) {
  const v = el?.dataset?.[name];
  try { return v == null ? fallback : JSON.parse(v); }
  catch { return fallback; }
}
JavaScript

ここが重要です:読み取りと型変換をユーティリティ化すると、未設定・空文字・型ミスによる事故を全体で防げます。名前は camelCase(dataset のキー名)で渡す前提に揃えます。


まとめ

data-* 属性は、要素に“任意の文字列データ”を安全に持たせるための標準手段です。DOM からは dataset(camelCase)か getAttribute/setAttribute(生の文字列)で扱い、数値・真偽・複合データは必ず意図した形へ変換します。命名は接頭辞で範囲と意味を明確にし、未設定/空文字の扱いをプロジェクトで統一。イベント委譲や関連要素参照、動的リンク生成などで data-* を“材料”として活用し、最終的な表示や構造は DOM API と CSS に任せる。この型を身につければ、初心者でも安全・明快にデータを持たせ、UI を意図通りに制御できます。

タイトルとURLをコピーしました