前端实用技巧:一行代码实现行数据转树形结构
在前端开发中,处理层级数据是非常常见的需求,比如构建菜单导航、组织部门架构、展示分类目录等场景,都需要将扁平的行数据转换为嵌套的树形结构。本文将详细拆解一个简洁高效的行转树工具函数,帮助你理解其核心逻辑,并能灵活应用到实际开发中。
一、函数功能与使用场景
先来看我们要实现的核心函数 list2tree ,它的核心作用是: 将包含父级关联关系的扁平数组(行数据),转换为带有层级嵌套的树形结构数组 。
适用场景包括但不限于:
- 前端菜单渲染(一级菜单、二级菜单嵌套)
- 部门/组织架构展示
- 商品分类层级展示
- 评论区的嵌套回复结构
二、完整函数代码
先贴出完整的函数代码,方便你整体感知:
/**
* 将扁平的行数据转换为树形结构
* @param {Array} list - 原始扁平数组
* @param {Number} parent_id - 根节点的父 ID,默认 0
* @param {String} target_field - 节点唯一标识字段,默认'id'
* @param {String} parent_field - 父节点关联字段,默认'parent_id'
* @returns {Array} 嵌套的树形结构数组
*/
list2tree(list, parent_id = 0, target_field = 'id', parent_field = 'parent_id') {
// 1. 创建节点映射对象,用于快速查找节点
const menuObj = {};
list.forEach(item => {
// 处理父 ID 为空的情况,默认赋值 0
item[parent_field] = item[parent_field] || 0;
// 以节点唯一标识为键,存储节点本身,方便后续快速查找
menuObj[item[target_field]] = item;
});
// 2. 筛选根节点并构建子节点嵌套关系
return list.filter(item => {
// 判断当前节点是否为根节点(父 ID 不等于根节点父 ID)
if (item[parent_field] !== parent_id) {
// 找到当前节点的父节点,为其添加 children 属性
if ('children' in menuObj[item[parent_field]]) {
// 父节点已有 children,直接 push 当前节点
menuObj[item[parent_field]].children.push(item);
} else {
// 父节点无 children,初始化数组并放入当前节点
menuObj[item[parent_field]].children = [item];
}
// 非根节点过滤掉,最终只返回根节点数组
return false;
}
// 根节点保留,返回 true
return true;
});
},三、代码逐段拆解说明
1. 函数参数定义
list2tree(list, parent_id = 0, target_field = 'id', parent_field = 'parent_id') {list:必传参数,需要转换的原始扁平数组,每一项是包含节点信息的对象;parent_id:可选参数,根节点的父 ID 值,默认 0(可根据业务调整,比如部分场景根节点父 ID 为 null 或'');target_field:可选参数,节点的唯一标识字段,默认'id'(比如业务中可能用'uid'、'menuId'等);parent_field:可选参数,节点关联父节点的字段,默认'parent_id'(比如业务中可能用'pid'、'parentId'等)。
2. 构建节点映射对象
const menuObj = {};
list.forEach(item => {
// 兼容父 ID 为空的情况,统一赋值为 0,避免匹配不到根节点
item[parent_field] = item[parent_field] || 0;
// 以节点唯一 ID 为键,存储节点本身,O(1) 时间复杂度查找节点
menuObj[item[target_field]] = item;
});这一步是核心优化点:
- 遍历原始数组,将每个节点以「唯一 ID-节点对象」的形式存入
menuObj; - 后续查找父节点时无需再次遍历数组,直接通过键值对获取,将时间复杂度从 O(n²) 优化为 O(n);
- 同时处理父 ID 为空的边界情况,统一赋值为 0,避免因父 ID 为 null/undefined 导致的匹配失败。
3. 筛选根节点+构建嵌套关系
return list.filter(item => {
if (item[parent_field] !== parent_id) {
// 非根节点:找到父节点并添加到 children 中
if ('children' in menuObj[item[parent_field]]) {
menuObj[item[parent_field]].children.push(item);
} else {
menuObj[item[parent_field]].children = [item];
}
return false; // 过滤掉非根节点
}
return true; // 保留根节点
});这一步通过 filter 方法完成两个核心操作:
- 筛选根节点 :判断节点的父 ID 是否等于预设的根节点父 ID(默认 0),等于则为根节点,保留;
- 构建子节点嵌套 :非根节点找到对应的父节点,为父节点添加
children属性,将当前节点放入父节点的children数组中; filter方法的特性:返回true的项会保留在结果数组中,返回false的项会被过滤,最终结果只包含根节点,且每个根节点下已嵌套好所有子节点。
四、使用示例
1. 准备原始数据
const rawList = [
{ id: 1, name: '首页', parent_id: 0 },
{ id: 2, name: '用户管理', parent_id: 0 },
{ id: 3, name: '用户列表', parent_id: 2 },
{ id: 4, name: '新增用户', parent_id: 3 },
{ id: 5, name: '角色管理', parent_id: 2 }
];2. 调用函数转换
const treeData = list2tree(rawList);
console.log(treeData);3. 输出结果
[
{ id: 1, name: '首页', parent_id: 0 },
{
id: 2,
name: '用户管理',
parent_id: 0,
children: [
{
id: 3,
name: '用户列表',
parent_id: 2,
children: [{ id: 4, name: '新增用户', parent_id: 3 }]
},
{ id: 5, name: '角色管理', parent_id: 2 }
]
}
]五、扩展与优化建议
- 支持多级嵌套 :当前函数天然支持无限层级嵌套,只要原始数据的父 ID 关联正确,即可生成任意深度的树形结构;
- 空数据处理 :可添加入参校验,避免传入空数组时报错:
if (!Array.isArray(list) || list.length === 0) return [];- 防循环引用 :如果原始数据中存在父 ID 指向自身的异常数据,可添加判断:
if (item[target_field] === item[parent_field]) return false;- 自定义 children 字段 :可新增参数
children_field,让用户自定义子节点字段名(比如'child'、'subMenu')。
总结
- 核心逻辑:先通过映射对象实现节点快速查找,再通过 filter 筛选根节点并构建子节点嵌套,时间复杂度优化至 O(n);
- 灵活性:通过参数配置支持自定义节点唯一标识、父关联字段、根节点父 ID,适配不同业务场景;
- 边界处理:兼容父 ID 为空的情况,避免转换失败,保证函数的健壮性。
这个工具函数代码简洁、性能高效,可直接集成到你的前端项目中,解决大部分行数据转树形结构的需求。
发布评论
发布评论前请先 登录。
评论列表 0

暂无评论



