
本文详解如何用 d3.js 基于嵌套 json 数据动态生成符合语义结构的侧边栏导航(含标题、菜单项及下拉子菜单),解决嵌套数据绑定报错问题,并提供可直接运行的完整示例。
本文详解如何用 d3.js 基于嵌套 json 数据动态生成符合语义结构的侧边栏导航(含标题、菜单项及下拉子菜单),解决嵌套数据绑定报错问题,并提供可直接运行的完整示例。
在构建现代化单页应用(SPA)或管理后台时,一个灵活、可配置的侧边栏导航是常见需求。D3.js 虽以数据可视化见长,但其强大的数据绑定(data())、选择(select/selectAll)与进入(enter())机制,同样适用于动态 DOM 构建——尤其适合处理层级化导航数据。
核心挑战在于:D3 不支持跨层级直接链式绑定嵌套数组(如 d.hasPages)。原代码中 dropdownItems.data(d => d.hasItems) 报错 undefined is not iterable,正是因为部分数据项(如 header 或无子页的 menu item)根本不存在 hasPages 字段,返回 undefined,而 selectAll(...).data(undefined) 会触发迭代错误。
✅ 正确解法是:在 .each() 中按类型分支处理,对每个
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 Sidebar Navigation</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.sidebar-nav { list-style: none; padding: 0; margin: 0; }
.sidebar-header { font-weight: bold; padding: 12px 16px; color: #6c757d; text-transform: uppercase; font-size: 0.8rem; }
.sidebar-item > a { display: block; padding: 8px 16px; text-decoration: none; color: #343a40; }
.sidebar-item > a:hover { background-color: #f8f9fa; }
.sidebar-dropdown { list-style: none; padding-left: 24px; margin: 0; }
.sidebar-dropdown .sidebar-item > a { padding-left: 32px; font-size: 0.9rem; }
</style>
</head>
<body>
<ul class="sidebar-nav"></ul>
<script>
const sidenavData = [
{ 'name': 'Header name 1', 'type': 'header' },
{
'name': 'Menu item 1',
'type': 'item',
'hasPages': [{ 'name': 'Page 1' }, { 'name': 'Page 2' }]
},
{
'name': 'Menu item 2',
'type': 'item',
'hasPages': [{ 'name': 'Report 1' }, { 'name': 'Report 2' }, { 'name': 'Report 3' }]
},
{ 'name': 'Header name 2', 'type': 'header' },
{ 'name': 'Notifications', 'type': 'item' },
{ 'name': 'Messages', 'type': 'item' }
];
const nav = d3.select('ul.sidebar-nav');
nav.selectAll('li')
.data(sidenavData)
.enter()
.append('li')
.attr('class', d => d.type === 'header' ? 'sidebar-header' : 'sidebar-item')
.each(function(d) {
const li = d3.select(this);
if (d.type === 'header') {
// 纯文本标题
li.text(d.name);
}
else if (d.hasPages && Array.isArray(d.hasPages) && d.hasPages.length > 0) {
// 菜单项 + 下拉菜单
li.append('a')
.attr('class', 'sidebar-link')
.text(d.name);
const dropdown = li.append('ul')
.attr('class', 'sidebar-dropdown');
dropdown.selectAll('li')
.data(d.hasPages)
.enter()
.append('li')
.attr('class', 'sidebar-item')
.append('a')
.attr('class', 'sidebar-link')
.text(page => page.name);
}
else {
// 普通菜单项(无子页)
li.append('a')
.attr('class', 'sidebar-link')
.text(d.name);
}
});
</script>
</body>
</html>? 关键要点说明:
- ✅ 使用 .each() 实现逐元素逻辑分支,避免在 data() 中传入可能为 undefined 的值;
- ✅ 显式校验 Array.isArray(d.hasPages) && d.hasPages.length > 0,增强健壮性(防止空数组或 null 导致静默失败);
- ✅ 所有 标签统一挂载在
- 内部,符合 HTML 语义规范(
- 直接包含 是合法且推荐的);
- ✅ CSS 样式已内联提供基础视觉反馈(悬停、缩进、字体层级),可按需扩展为完整主题系统;
- ⚠️ 注意:若需支持点击跳转,应在 上添加 href="#" 或 onclick 事件处理器(如调用路由方法),此处聚焦 DOM 结构生成。
该方案简洁、可维护性强,无需引入额外模板引擎,完美契合 D3 “数据即源”的设计哲学。当导航结构随后端 API 动态变化时,仅需更新 sidenavData 并重新调用渲染逻辑即可,真正实现声明式、响应式的导航构建。











