纯TypeScript硬编码表格:无插件实现分页与搜索功能逻辑咨询
嘿,这事儿不难!纯原生TypeScript就能搞定表格的搜索和分页,完全不用任何插件。我给你拆解下实现逻辑,再附上完整的代码示例,你照着改就行~
整体核心思路
我们需要维护三组数据/状态:
- 原始硬编码数据:作为数据源,永远不动,保证搜索和分页的基础数据完整
- 过滤后的数据:用户搜索后,从原始数据中筛选出的匹配结果
- 当前分页数据:根据当前页码和每页条数,从过滤后的数据中截取的要显示的条目
每次搜索或者切换页码时,我们只需要更新对应的状态,然后重新渲染表格和分页控件即可。
搜索功能实现逻辑
- 添加搜索输入框:在HTML里加一个文本输入框,用于接收用户的搜索关键词
- 监听输入事件:给输入框绑定
input事件,实时响应用户的输入 - 过滤数据:把用户输入的关键词转成小写(忽略大小写),遍历原始数据,匹配你指定的列(比如姓名、邮箱等),得到过滤后的数据集
- 重置分页状态:搜索完成后,把当前页码重置为1,避免用户在第3页搜索后还停留在第3页(可能没有数据)
- 重新渲染:根据过滤后的数据重新计算分页并渲染表格
分页功能实现逻辑
- 定义分页参数:设置每页显示的条数(比如
pageSize = 5),维护当前页码(currentPage = 1) - 计算总页数:用过滤后的数据总条数除以每页条数,向上取整得到总页数(
Math.ceil(filteredData.length / pageSize)) - 渲染分页控件:生成上一页按钮、页码按钮组、下一页按钮,给按钮绑定点击事件
- 处理分页切换:点击页码或上下页时,更新
currentPage,然后从过滤后的数据中截取对应区间的条目(slice((currentPage-1)*pageSize, currentPage*pageSize)) - 边界处理:当前页是第一页时禁用上一页按钮,当前页是最后一页时禁用下一页按钮;如果没有数据或者只有一页,隐藏分页控件
完整代码示例
HTML部分
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>原生TS表格分页搜索</title> <style> .table-controls { margin: 1rem 0; } #searchInput { padding: 0.5rem; width: 300px; border: 1px solid #ddd; border-radius: 4px; } table { border-collapse: collapse; width: 100%; margin-top: 1rem; } th, td { border: 1px solid #ddd; padding: 0.8rem; text-align: left; } th { background-color: #f5f5f5; font-weight: 600; } .pagination { margin-top: 1rem; display: flex; gap: 0.3rem; align-items: center; } .pagination button { padding: 0.5rem 1rem; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; background-color: white; } .pagination button:disabled { cursor: not-allowed; opacity: 0.6; } .page-btn.active { background-color: #007bff; color: white; border-color: #007bff; } </style> </head> <body> <div class="table-controls"> <input type="text" id="searchInput" placeholder="搜索姓名或邮箱..." /> </div> <table id="dataTable"> <thead> <tr> <th>ID</th> <th>姓名</th> <th>邮箱</th> </tr> </thead> <tbody id="tableBody"></tbody> </table> <div id="paginationControls" class="pagination"></div> <script src="./table.ts"></script> </body> </html>
TypeScript部分(table.ts)
// 定义表格数据类型 interface TableRow { id: number; name: string; email: string; } // 硬编码的原始数据 const originalRows: TableRow[] = [ { id: 1, name: "张三", email: "zhangsan@example.com" }, { id: 2, name: "李四", email: "lisi@example.com" }, { id: 3, name: "王五", email: "wangwu@example.com" }, { id: 4, name: "赵六", email: "zhaoliu@example.com" }, { id: 5, name: "孙七", email: "sunqi@example.com" }, { id: 6, name: "周八", email: "zhouba@example.com" }, { id: 7, name: "吴九", email: "wujiu@example.com" }, { id: 8, name: "郑十", email: "zhengshi@example.com" }, { id: 9, name: "冯十一", email: "fengshiyi@example.com" }, { id: 10, name: "陈十二", email: "chenshier@example.com" }, ]; // 状态变量 let filteredRows: TableRow[] = [...originalRows]; let currentPage = 1; const pageSize = 3; // 每页显示3条 // 渲染表格内容 function renderTable(data: TableRow[]) { const tableBody = document.getElementById("tableBody"); if (!tableBody) return; tableBody.innerHTML = ""; data.forEach(row => { const tr = document.createElement("tr"); tr.innerHTML = ` <td>${row.id}</td> <td>${row.name}</td> <td>${row.email}</td> `; tableBody.appendChild(tr); }); } // 设置搜索功能 function setupSearch() { const searchInput = document.getElementById("searchInput") as HTMLInputElement; if (!searchInput) return; searchInput.addEventListener("input", (e) => { const keyword = (e.target as HTMLInputElement).value.toLowerCase().trim(); // 过滤匹配姓名或邮箱 filteredRows = originalRows.filter(row => row.name.toLowerCase().includes(keyword) || row.email.toLowerCase().includes(keyword) ); currentPage = 1; // 搜索后回到第一页 renderPagination(); renderCurrentPageData(); }); } // 获取当前页要显示的数据 function getCurrentPageData(): TableRow[] { const startIdx = (currentPage - 1) * pageSize; const endIdx = startIdx + pageSize; return filteredRows.slice(startIdx, endIdx); } // 渲染当前页数据 function renderCurrentPageData() { const pageData = getCurrentPageData(); renderTable(pageData); } // 渲染分页控件 function renderPagination() { const paginationContainer = document.getElementById("paginationControls"); if (!paginationContainer) return; const totalPages = Math.ceil(filteredRows.length / pageSize); // 没有数据或只有一页时隐藏分页 if (totalPages <= 1) { paginationContainer.innerHTML = ""; return; } let paginationHTML = ` <button id="prevBtn" ${currentPage === 1 ? "disabled" : ""}>上一页</button> `; // 渲染页码按钮(可以优化成只显示当前页前后的页码,避免太多按钮) for (let i = 1; i <= totalPages; i++) { paginationHTML += ` <button class="page-btn ${i === currentPage ? "active" : ""}" data-page="${i}">${i}</button> `; } paginationHTML += ` <button id="nextBtn" ${currentPage === totalPages ? "disabled" : ""}>下一页</button> `; paginationContainer.innerHTML = paginationHTML; // 绑定上一页点击事件 document.getElementById("prevBtn")?.addEventListener("click", () => { if (currentPage > 1) { currentPage--; renderPagination(); renderCurrentPageData(); } }); // 绑定下一页点击事件 document.getElementById("nextBtn")?.addEventListener("click", () => { if (currentPage < totalPages) { currentPage++; renderPagination(); renderCurrentPageData(); } }); // 绑定页码按钮点击事件 document.querySelectorAll(".page-btn").forEach(btn => { btn.addEventListener("click", (e) => { const pageNum = parseInt((e.target as HTMLElement).dataset.page || "1"); currentPage = pageNum; renderPagination(); renderCurrentPageData(); }); }); } // 初始化页面 function init() { setupSearch(); renderPagination(); renderCurrentPageData(); } // 页面加载完成后执行初始化 window.addEventListener("DOMContentLoaded", init);
额外优化建议
- 如果你表格列很多,可以把搜索匹配的列做成可配置的数组,比如
searchableColumns = ["name", "email"],然后遍历这个数组进行匹配,更灵活 - 当页码很多时(比如几十页),不要全部渲染页码按钮,可以只显示当前页的前2页、后2页,加上首尾页按钮,避免分页控件太长
- 可以给搜索加个防抖,避免用户每输入一个字符就触发一次过滤,提升性能(比如设置300ms延迟)
内容的提问来源于stack exchange,提问作者Harshit Kargeti
相关产品推荐
相关产品推荐

