JS深度嵌套数据过滤:返回父级/直接子项的实现方案问询
深度嵌套数据过滤:保留父级+目标节点的最优实现方案
嘿,我完全懂处理这种深度嵌套数据时的头疼感——尤其是还要对接不同场景的筛选器,刚开始确实容易摸不着头绪。咱们一步步拆解这个问题,从核心思路到具体实现,再到适配不同场景的技巧,都给你理清楚。
核心思路先明确
首先得把需求拆透:你要的是筛选出符合条件的节点,同时保留这些节点的所有父级(这样在UI里能展开看到目标节点),并且保留目标节点的直接子项(如果子项不需要额外筛选的话)。
举个直观的例子:假设你的数据是树形结构,当某个孙节点符合筛选条件,那它的父节点、祖父节点都要保留,同时这个孙节点的直接子节点(如果有的话)也得留下——不管子节点是否符合条件。
基础实现:递归法(中小数据量首选)
递归是处理嵌套结构最直观的方式,代码简洁易懂,适合数据量不大的场景。咱们直接上代码,带详细注释:
// 通用过滤函数:支持自定义筛选条件、自定义子节点字段名 function filterNestedTree(nodes, filterCondition, childrenKey = 'children') { return nodes.reduce((result, currentNode) => { // 递归处理当前节点的子节点 const filteredChildren = currentNode[childrenKey] ? filterNestedTree(currentNode[childrenKey], filterCondition, childrenKey) : []; // 判断是否需要保留当前节点:要么自身符合条件,要么子节点里有符合条件的(需要保留父级) const shouldKeep = filterCondition(currentNode) || filteredChildren.length > 0; if (shouldKeep) { // 复制原节点(避免修改原始数据),替换子节点为过滤后的结果 const keptNode = { ...currentNode }; if (currentNode[childrenKey]) { // 如果当前节点本身符合条件,直接保留所有直接子项;否则用过滤后的子节点 keptNode[childrenKey] = filterCondition(currentNode) ? [...currentNode[childrenKey]] : filteredChildren; } result.push(keptNode); } return result; }, []); }
怎么用?
假设你的REST返回的数据是这样的:
const apiData = [ { id: 1, name: "部门A", children: [ { id: 11, name: "小组A1", children: [{ id: 111, name: "张三", role: "前端" }] }, { id: 12, name: "小组A2", children: [{ id: 121, name: "李四", role: "后端" }] } ] }, { id: 2, name: "部门B", children: [{ id: 21, name: "王五", role: "前端" }] } ];
如果要筛选所有role === '前端'的节点,同时保留父级和直接子项:
// 定义筛选条件 const isFrontend = node => node.role === '前端'; // 执行过滤 const filteredResult = filterNestedTree(apiData, isFrontend);
得到的结果会是:
[ { id: 1, name: "部门A", children: [ { id: 11, name: "小组A1", children: [{ id: 111, name: "张三", role: "前端" }] } ] }, { id: 2, name: "部门B", children: [{ id: 21, name: "王五", role: "前端" }] } ]
大数据量优化:迭代+标记法
如果你的数据量特别大(比如上千个节点),递归可能会导致栈溢出。这时候可以用先标记所有需要保留的节点,再重构树形结构的方式:
function filterLargeNestedTree(nodes, filterCondition, childrenKey = 'children') { const keepNodeIds = new Set(); // 第一步:深度优先遍历,标记所有需要保留的节点(自身符合条件,或有后代符合) function markNodes(node) { let hasMatchingDescendant = false; // 先处理子节点 if (node[childrenKey]) { for (const child of node[childrenKey]) { if (markNodes(child)) { hasMatchingDescendant = true; } } } // 判断当前节点是否需要保留 const shouldKeep = filterCondition(node) || hasMatchingDescendant; if (shouldKeep) { keepNodeIds.add(node.id); // 假设每个节点有唯一id,没有的话可以用对象引用 } return shouldKeep; } // 标记所有节点 nodes.forEach(node => markNodes(node)); // 第二步:根据标记重构树形结构 function buildTree(treeNodes) { return treeNodes.reduce((result, node) => { if (keepNodeIds.has(node.id)) { const keptNode = { ...node }; if (node[childrenKey]) { keptNode[childrenKey] = buildTree(node[childrenKey]); } result.push(keptNode); } return result; }, []); } return buildTree(nodes); }
适配不同场景&筛选器
你的需求里提到要适配不同场景和下拉框筛选器,这里给两个实用技巧:
1. 动态切换筛选条件
把不同的筛选逻辑做成一个映射表,下拉框选中时直接取对应的条件即可:
// 筛选条件映射表,对应下拉框的选项值 const filterOptions = { 'frontend': node => node.role === '前端', 'backend': node => node.role === '后端', 'dept-a': node => node.name.includes('部门A'), 'name-contains-张': node => node.name?.includes('张') }; // 当下拉框选中"frontend"时: const selectedFilter = filterOptions['frontend']; const result = filterNestedTree(apiData, selectedFilter);
2. 适配不同的节点结构
如果REST返回的数据子节点字段不是children(比如是items或subNodes),只需要调用函数时传入第三个参数:
// 假设子节点字段是items const result = filterNestedTree(apiData, isFrontend, 'items');
关键注意点
- 不要修改原始数据:始终复制节点后再修改,避免影响其他地方使用数据
- 处理空值:比如节点可能没有子节点、字段可能不存在,要加可选链(
?.)或判断 - 性能考量:递归适合中小数据,大数据用迭代标记法;如果是超大量数据,可能需要后端配合做部分筛选
内容的提问来源于stack exchange,提问作者JSess
相关产品推荐
相关产品推荐

