应用场景
在业务中我们可能会开发树状组件或者用一些ui库的Tree组件,例如antd的Tree。如果不使用异步加载,通常后端会返回一个完整的树结构数据给前端,但也有例外的情况,后端给的是一个扁平化的数据,例如这样:
const arr = [
{id: 1, name: '部门1', pid: 0},
{id: 2, name: '部门2', pid: 4},
{id: 3, name: '部门3', pid: 1},
{id: 4, name: '部门4', pid: 3},
{id: 5, name: '部门5', pid: 4},
{id: 6, name: '部门1', pid: 0},
{id: 7, name: '部门1', pid: 6},
]
这个时候就需要我们去将其处理为一颗树结构数据了。
实现方式
-
第一种方式:递归实现。
递归的弊端在于存在性能瓶颈,大量数据的情况可能不太适合。
function customTree1 (arr, parentKey, pid = 0) { return arr.reduce((total, it) => { if (pid === it[parentKey]) { it.children = tree(arr, it.id) total.push(it) } return total }, []) }
-
第二种方式: 利用对象的弱引用实现
相对与递归,因为只有一层遍历,性能上要更好一些。写法有很多种,这里只列了两种写法,但实现思路都是一样的,利用map数据结构创建一个字典,遍历数据设置子节点。时间和空间复杂度没算过,有兴趣的可以自己算一下。
-
第一种:
function customTree2 (baseData, parentKey, rootId = 0) { const map = {} const result = [] arr.sort((a,b) => a[parentKey] - b[parentKey]) arr.forEach(it => { if (!map[it.id]) { map[it.id] = { children: [] } } map[it.id] = { ...it, children: map[it.id]['children'] } const parent = map[it.id] if (it[parentKey] === rootId) { result.push(parent) } else { if (map[it.pid] && map[it[parentKey]].children) { map[it[parentKey]].children.push(parent) } } }) return result }
-
第二种:
function customTree3 (baseData = [], parentKey, rootId = 0) { try { const result = [] // 创建一个数据字典 const dictionary = baseData.reduce((pre, cur) => { pre[cur.id] = { ...cur, children: [] } return pre }, {}) baseData.forEach(item => { const parent = dictionary[item[parentKey]] if (parent) parent.children.push(dictionary[item.id]) // 在字典中查找对应的数据并修改它的children else result.push(dictionary[rootId || item.id]) // 如果没找到就代表是根节点,或者指定一个根节点 }) return result } catch (e) { console.error(e) return [] } }
-
-
使用
const data1 = customTree1(arr, 'pid') const data2= customTree2 (arr, 'pid') const data3 = customTree3(arr, 'pid') console.log('🚀 --- ~ data1', data1) console.log('🚀 --- ~ data2', data2) console.log('🚀 --- ~ data3', data3)