なぜ今さら「リスト構造」なのか? ―― div教からの脱却と、セマンティクスの真価
フロントエンドの深淵に潜る諸兄であれば、`
- ` や `
- `
- ` (Unordered List):
- `
- ` (Ordered List):
- `
- ` (Description List):
- 定義リストのアイテムを厳格に管理する型定義
- 順不同のdiv連打ではなく、データ構造としてリストを定義する
- {term}
- {description}
- メモリ効率: Reactの `memo` や `useMemo` を活用しつつ、リストアイテムの `key` にはインデックスではなく一意のIDを使用すること。DOMの再利用効率が劇的に変わる。
- 非同期の競合: リストデータのフェッチ中に「ロード中」を表示する場合、`
- ` の中を空にするのではなく、`aria-busy=”true”` を付与することで、支援技術に対して「現在読み込み中である」という正確な状態を通知する。
- ` を「単なるスタイル付けのためのラッパー」と見なす甘美な誘惑に、一度は駆られたことがあるはずだ。「CSSで見た目さえ整えれば、`div` を並べた方がDOM構造がフラットになって管理しやすい」――かつての私もそう信じていた。
しかし、大規模なWebアプリケーションのアーキテクチャを設計する際、この「HTML軽視」こそが、将来的な技術負債とアクセシビリティの地獄への切符となる。ブラウザエンジンという巨大なブラックボックスは、HTMLのセマンティクスを通じて「ドキュメントの文脈」を解釈し、その上でレンダリングやアクセシビリティツリーの構築を行っている。
本稿では、単なるマークアップの作法を超え、レンダリング負荷や型安全性、そしてブラウザの内部挙動まで踏み込んだ「リスト構造の真髄」を解き明かそう。
—
1. ul, ol, dl を使い分けるという「意思決定」
まず基本を再確認しよう。これらは単なるHTMLタグではない。ブラウザに対して送る「このコンテンツはこういう関係性を持っている」という明示的なメタデータだ。
これらを使い分ける最大の理由は、スクリーンリーダーが「コンテキスト」をどう読み上げるかにある。例えば、`div` を並べただけのリストは、支援技術には「単なるテキストの羅列」としてしか認識されない。一方、`
- ` を使えば「○個のリストアイテムがあります」という情報をユーザーに先んじて提供できる。この数秒の認知コストの削減が、UXの堅牢性を決定づける。
—
2. div/span リストのアクセシビリティ・リスクとレンダリングの罠
「`div` でマークアップして、ARIAロール (`role=”list”`, `role=”listitem”`) を振ればいいじゃないか」という反論があるかもしれない。しかし、これには重大な落とし穴がある。
ブラウザ実装の差異と「嘘」の代償
ブラウザのアクセシビリティツリー生成プロセスにおいて、セマンティックなHTMLは「ネイティブな挙動」が保証されている。しかし、ARIAで強制的にロールを上書きした場合、ブラウザやスクリーンリーダーの組み合わせによっては、期待通りのフォーカス制御やキーボードナビゲーションが機能しないケースが散見される。特に、動的にDOMが書き換わるReactやVueのコンポーネント環境下では、ARIA属性の更新漏れ(競合)が致命的なバグを招く。
レンダリング負荷とリフロー
DOMノードの数そのものは変わらなくても、ブラウザのパーサーは `
- ` や `
- ` に遭遇した際、それを「リストコンテナ」として最適化された内部データ構造にマッピングする。無意味に `div` を重ねることは、CSSOM構築時の計算量を微増させるだけでなく、ブラウザの「レイアウト・エンジン」がコンテンツの文脈を把握する際のヒントを奪う行為だ。
—
3. TypeScriptによる厳格なリスト構造の抽象化
大規模開発において、リストのデータ構造をTypeScriptで守ることは極めて重要だ。特に `
- ` のような「対」をなす構造を、型レベルで強制する設計を見てみよう。
/
/
interface DefinitionItem {
term: string;
description: string;
}
const settingsData: DefinitionItem[] = [
{ term: ‘API_TIMEOUT’, description: ‘3000ms’ },
{ term: ‘MAX_RETRY’, description: ‘3’ },
];
// コンポーネント側での安全なレンダリング
const SettingsList: React.FC<{ items: DefinitionItem[] }> = ({ items }) => (
-
{items.map(({ term, description }, index) => (
))}
);
このように `dt` と `dd` をペアで扱う構造を型で縛れば、非同期で取得したデータが壊れていた際に、レンダリングフェーズで即座にエラーを検知できる。これは、「とりあえず表示できればいい」という甘い実装からは生まれない、堅牢なアーキテクチャだ。
—
4. パフォーマンス最適化とエッジケースの回避
最後に、パフォーマンスの観点から。大規模なリストを扱う際、仮想リスト(Virtual Scrolling)を導入する場合でも、その内側のマークアップはセマンティックであるべきだ。
現場の結論
「`div` で囲むのが一番早い」というのは、短期的には真実だが、長期的にはブラウザの最適化恩恵を拒否し、アクセシビリティという名の技術負債を積み重ねる行為だ。
我々スペシャリストに求められているのは、単に「動くもの」を作ることではなく、ブラウザというプラットフォームの仕様を深く理解し、そのポテンシャルを最大限に引き出すための「正しい地図」を描くことである。リストタグは、その地図の中でも最も基本的で、かつ最も強力な武器の一つだ。
明日からの実装、まずは `div` を消すことから始めてみてはどうだろうか。そこには、驚くほどクリーンで、ブラウザが喜びそうな美しい構造が待っているはずだ。

コメント