原生 JavaScript 实现动态任务筛选:事件委托、状态管理与实时搜索

原生 JavaScript 实现动态任务筛选:事件委托、状态管理与实时搜索

在前端项目中,任务管理、订单后台和数据列表经常需要提供多条件筛选功能。例如,用户可以根据任务状态切换列表,同时通过搜索框快速定位目标记录。

这类功能不一定需要引入 Vue 或 React。对于结构相对简单的页面,使用原生 JavaScript、DOM API 和事件委托,就可以构建可维护的交互组件。

本文以一个任务管理面板为例,实现状态筛选、实时搜索和结果数量统计。

一、设计组件结构

首先创建一个任务管理区域,包含五个状态筛选按钮、搜索输入框和任务列表。

<section class="task-panel" data-task-app>
  <h2>任务管理面板</h2>

  <div class="task-toolbar" role="group" aria-label="任务状态筛选">
    <button
      type="button"
      id="https://github.com/tiktokwuyu-ux/TIKTOK_baike/commit/36e141169c80f56ddb0e450d14c6202c950a269d"
      data-filter="all"
      aria-pressed="true">
      全部任务
    </button>

    <button
      type="button"
      class="https://github.com/tiktokwuyu-ux/TIKTOK_baike/commit/122aeaf76134cd9d0f862b62537496bee4dbae67"
      data-filter="pending"
      aria-pressed="false">
      待处理
    </button>

    <button
      type="button"
      id="https://github.com/tiktokwuyu-ux/TIKTOK_baike/commit/0770149d2f4d156370408074c6ed6cb0ea70f84e"
      data-filter="running"
      aria-pressed="false">
      进行中
    </button>

    <button
      type="button"
      class="https://github.com/tiktokwuyu-ux/TIKTOK_baike/commit/08934b0f4d25e34814c103518328f2d0bf9fde8c"
      data-filter="paused"
      aria-pressed="false">
      已暂停
    </button>

    <button
      type="button"
      id="https://github.com/tiktokwuyu-ux/TIKTOK_baike/commit/e5447e0c2476c4b79e2f9ff8d6f998e386bdae9a"
      data-filter="done"
      aria-pressed="false">
      已完成
    </button>
  </div>

  <input
    type="search"
    data-search
    placeholder="搜索任务名称"
    aria-label="搜索任务名称"
  >

  <p data-result-count aria-live="polite"></p>

  <ul class="task-list" data-task-list>
    <li data-status="pending">检查用户提交的数据</li>
    <li data-status="running">同步远程服务配置</li>
    <li data-status="paused">整理历史任务记录</li>
    <li data-status="done">完成数据库结构检查</li>
    <li data-status="running">处理接口响应数据</li>
    <li data-status="pending">验证前端页面兼容性</li>
  </ul>
</section>

整个组件使用 data-* 属性描述交互行为。

data-filter 表示筛选按钮对应的状态,data-status 表示任务本身的状态。

这种设计能够将业务状态与样式选择器分离,减少后续修改 CSS 时对 JavaScript 逻辑的影响。

二、使用统一状态对象管理筛选条件

筛选功能通常包含多个条件。为了避免不同事件处理函数分别修改 DOM,可以先定义一个统一状态对象。

const app = document.querySelector("[data-task-app]");

const state = {
  filter: "all",
  keyword: ""
};

const taskList = app.querySelector("[data-task-list]");
const resultCount = app.querySelector("[data-result-count]");
const filterButtons = app.querySelectorAll("[data-filter]");
const searchInput = app.querySelector("[data-search]");

其中,filter 负责保存当前选中的任务状态,keyword 保存用户输入的搜索关键词。

当其中任意一个条件发生变化时,都交给同一个渲染函数处理。

这样可以避免不同筛选条件之间出现状态不一致的问题。

三、实现统一的筛选函数

接下来遍历任务列表,根据状态和关键词判断每条任务是否应该显示。

function renderTasks() {
  const tasks = taskList.querySelectorAll("[data-status]");

  let visibleCount = 0;

  for (const task of tasks) {
    const matchesStatus =
      state.filter === "all" ||
      task.dataset.status === state.filter;

    const matchesKeyword = task.textContent
      .toLocaleLowerCase("zh-CN")
      .includes(state.keyword);

    const visible = matchesStatus && matchesKeyword;

    task.hidden = !visible;

    if (visible) {
      visibleCount++;
    }
  }

  resultCount.textContent = `当前显示 ${visibleCount} 项任务`;

  for (const button of filterButtons) {
    const selected = button.dataset.filter === state.filter;

    button.setAttribute("aria-pressed", String(selected));
  }
}

这里有两个关键设计。

第一,使用 hidden 属性控制任务显示状态,不需要为每条任务反复设置内联样式。

第二,状态和关键词采用逻辑与关系。只有同时满足当前状态和搜索条件,任务才会显示。

当用户切换状态时,原来的搜索关键词仍然保留。

四、通过事件委托处理筛选按钮

传统做法通常为每个按钮分别绑定一个点击事件。

当按钮数量增加,或者页面需要动态插入新的筛选项时,这种方式维护起来比较麻烦。

使用事件委托,可以直接在父容器监听点击操作。

app.addEventListener("click", (event) => {
  const button = event.target.closest("button[data-filter]");

  if (!button || !app.contains(button)) {
    return;
  }

  state.filter = button.dataset.filter;

  renderTasks();
});

closest() 会从事件目标开始,向上查找符合选择器的元素。

这意味着即使按钮内部包含图标或其他子元素,点击事件仍然可以正确定位到对应按钮。

相比于在多个按钮中重复编写事件处理函数,这种方式能够将交互逻辑集中到一个位置。

五、使用防抖优化搜索输入

用户输入关键词时,input 事件可能在短时间内连续触发。

如果每次输入都执行复杂的数据过滤,可能造成不必要的计算。

可以通过防抖机制控制执行频率。

function debounce(callback, delay) {
  let timer;

  return (...args) => {
    clearTimeout(timer);

    timer = setTimeout(() => {
      callback(...args);
    }, delay);
  };
}

const handleSearch = debounce((value) => {
  state.keyword = value
    .trim()
    .toLocaleLowerCase("zh-CN");

  renderTasks();
}, 200);

searchInput.addEventListener("input", (event) => {
  handleSearch(event.target.value);
});

这里的 200 表示输入停止约 200 毫秒后再执行搜索。

实际项目中,防抖延迟应根据列表规模、输入体验和计算成本调整。

需要注意,防抖更适合搜索建议和非即时性筛选。如果操作需要立即反馈,则不一定适合增加延迟。

六、补充基础 CSS

为了让筛选状态更加直观,可以根据 aria-pressed 属性设置按钮样式。

.task-panel {
  max-width: 760px;
  margin: 0 auto;
  padding: 24px;
}

.task-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 20px;
}

.task-toolbar button {
  padding: 8px 14px;
  border: 1px solid #d0d7de;
  border-radius: 6px;
  background: #fff;
  cursor: pointer;
}

.task-toolbar button[aria-pressed="true"] {
  background: #0969da;
  border-color: #0969da;
  color: #fff;
}

[data-search] {
  width: 100%;
  padding: 10px;
  box-sizing: border-box;
}

.task-list {
  padding-left: 20px;
}

.task-list li {
  padding: 10px 0;
  border-bottom: 1px solid #eee;
}

.task-list li[hidden] {
  display: none;
}

使用属性选择器可以直接关联组件当前状态,无需在 JavaScript 中额外维护一套样式类名。

同时,aria-pressed 还能为辅助技术提供按钮选中状态。

七、处理动态添加的任务

当业务系统通过接口获取新数据时,任务列表可能随时发生变化。

如果组件采用事件委托,就不需要重新绑定筛选按钮的监听器。

例如,可以定义一个统一的任务插入函数:

function appendTask(task) {
  const item = document.createElement("li");

  item.dataset.status = task.status;
  item.textContent = task.title;

  taskList.appendChild(item);

  renderTasks();
}

这里使用 textContent 设置任务标题,而不是直接拼接 innerHTML。

这种方式能够避免把用户输入当作 HTML 解析,降低动态内容带来的注入风险。

新增任务后调用 renderTasks(),就能自动应用当前的搜索和状态筛选条件。

八、性能与可维护性建议

当任务数量较少时,直接遍历 DOM 元素通常能够满足性能要求。

但如果数据量达到数千甚至数万条,频繁访问 DOM 可能增加渲染成本。

实际开发中,可以根据业务规模逐步优化:

  1. 维护独立数据模型:将任务数据保存在 JavaScript 对象中,DOM 只负责展示。
  2. 使用分页或虚拟列表:避免一次性渲染大量节点。
  3. 减少重复查询:缓存生命周期内稳定的 DOM 引用。
  4. 统一状态更新入口:所有筛选行为最终都经过同一个渲染流程。
  5. 处理异步请求竞争:远程筛选时可结合 AbortController 取消过期请求。

这些优化应根据实际性能测试结果决定,不需要在简单组件中提前引入复杂架构。

九、总结

使用原生 JavaScript 构建动态筛选组件,核心不是监听多少个按钮,而是如何合理组织状态、事件和渲染逻辑。

通过 data-* 属性描述业务状态、事件委托处理交互、统一渲染函数管理页面,以及防抖减少重复计算,可以建立一个结构清晰、容易扩展的任务管理组件。

对于后台管理系统、数据筛选页面和轻量级业务工具,这套方案能够在不引入额外框架的情况下满足常见交互需求。

posted @ 2026-11-12 17:20  YXTK招募助手  阅读(4)  评论(0)    收藏  举报