本文介绍一种基于 data-filter 属性与现代 DOM API 的声明式过滤方案,通过统一管理按钮状态和元素可见性,彻底避免为每个元素单独增删 active/hidden 类的冗余代码。
本文介绍一种基于 `data-filter` 属性与现代 dom api 的声明式过滤方案,通过统一管理按钮状态和元素可见性,彻底避免为每个元素单独增删 `active`/`hidden` 类的冗余代码。
在构建多类别内容筛选功能(如按“早餐”“午餐”“饮品”分类展示食物卡片)时,初学者常采用硬编码方式——为每个目标元素逐一调用 classList.add() 和 classList.remove()。这种方式不仅代码冗长、难以维护,更违背了“单一职责”与“可扩展性”原则。下面我们将重构这一逻辑,实现真正干净、可复用的过滤系统。
✅ 核心思路:声明式数据驱动 + 批量 DOM 操作
不再手动控制每个元素,而是:
为按钮添加 data-filter="xxx" 属性,标识其过滤意图;
为内容项添加 data-filter="breakfast" 或 data-filter="breakfast,lunch"(支持多标签);
使用 document.querySelectorAll() 批量获取元素,配合 forEach() 和 toggle() 实现状态同步。
? 示例代码(完整可运行)
⚠️ 注意事项与最佳实践
健壮性增强
:使用可选链 item.dataset.filter?.split(',') 防止未设置 data-filter 导致报错;
性能友好
:querySelectorAll 返回静态 NodeList,适合一次性遍历,避免重复查询;
可扩展性强
:新增分类只需增加一个带 data-filter 的按钮和对应 data-filter 值的内容项,无需修改 JS 逻辑;
CSS 解耦
:显示/隐藏完全由 .active 类控制,便于后续改用 opacity、transform 或 CSS 动画过渡;
无障碍友好
:建议为按钮添加 aria-pressed 属性(如 this.setAttribute('aria-pressed', 'true')),提升可访问性。
✅ 总结
用 data-* 属性承载业务语义,用 forEach + toggle 替代重复增删类名,既显著减少代码量,又提升了逻辑清晰度与可维护性。这种模式适用于任何「多态筛选」场景——无论是商品列表、博客标签,还是仪表盘模块切换。真正的“干净代码”,不在于行数少,而在于意图明确、变化局部、易于推演。
燕麦杯
三明治
意面
橙汁
酸奶果昔
冰咖啡
/* 默认隐藏非 active 元素 */
.filter > div:not(.active) {
display: none;
}
/* 按钮高亮样式 */
.filter-buttons button.active {
border-bottom: 2px solid #007bff;
font-weight: bold;
}// 统一绑定按钮事件
document.querySelectorAll('.filter-buttons button').forEach(button => {
button.addEventListener('click', function () {
// 1. 清除所有按钮的 active 状态
document.querySelectorAll('.filter-buttons button').forEach(btn =>
btn.classList.remove('active')
);
// 2. 当前按钮设为 active
this.classList.add('active');
// 3. 获取当前筛选类型
const filterType = this.dataset.filter;
// 4. 批量处理所有内容项
document.querySelectorAll('.filter > div').forEach(item => {
if (filterType === 'all') {
item.classList.add('active');
} else {
// 支持多值匹配:如 data-filter="breakfast,lunch"
const itemFilters = item.dataset.filter?.split(',') || [];
item.classList.toggle('active', itemFilters.includes(filterType));
}
});
});
});