数组转化为树
我们在做后台管理系统的项目时候经常用到一个问题就是权限问题。比如前端的路由,我们从后端获取的数据经常是以数组的形式返回的。
比如:
"menu_list": [{"id":1,"menu_name":"首页","pid":-1,"logo":null,"rank":1,"link":"home"},{"id":2,"menu_name":"企业信息","pid":-1,"logo":null,"rank":2,"link":"manager"},{"id":3,"menu_name":"财务管理","pid":-1,"logo":null,"rank":4,"link":"finance"},{"id":4,"menu_name":"订单管理","pid":-1,"logo":null,"rank":5,"link":"bills"},{"id":5,"menu_name":"设置","pid":-1,"logo":null,"rank":8,"link":"setting"},{"id":6,"menu_name":"员工管理","pid":2,"logo":null,"rank":1,"link":"manager/employee/list"},{"id":7,"menu_name":"角色管理","pid":2,"logo":null,"rank":2,"link":"manager/role/list"},{"id":8,"menu_name":"企业钱包","pid":3,"logo":null,"rank":1,"link":"finance/wallet"},{"id":9,"menu_name":"财务对账","pid":3,"logo":null,"rank":2,"link":"finance/check"},{"id":10,"menu_name":"系统设置","pid":5,"logo":null,"rank":1,"link":"setting/system"},{"id":17,"menu_name":"用车报告","pid":-1,"logo":null,"rank":6,"link":"report"},{"id":18,"menu_name":"下单约车","pid":-1,"logo":null,"rank":7,"link":"createOrder"},{"id":19,"menu_name":"部门管理","pid":2,"logo":null,"rank":3,"link":"manager/department"},{"id":20,"menu_name":"项目管理","pid":2,"logo":null,"rank":4,"link":"manager/projects"},{"id":21,"menu_name":"用车管控","pid":-1,"logo":null,"rank":3,"link":"manage_control"},{"id":22,"menu_name":"用车制度","pid":21,"logo":null,"rank":1,"link":"manage_control_list"}]
我们需要将这种数据转化为前端可用的数据,树的结构。
我们就需要一个方法,ArrayToTree。
我们先说说传统的方法:
我们就使用上边的数据:我们定义 arr等于menu_list。
方法一:递归实现
/**
* 递归查找,获取children
*/
const getChildren = (data, result, pid) => {
for (const item of data) {
if (item.pid === pid) {
const newItem = { ...item, children: [] };
result.push(newItem);
getChildren(data, newItem.children, item.id);
} else if (item.id === -1) {
result.push(item);
}
}
};
/**
* 转换方法
*/
const arrayToTree = (data, pid) => {
const result = [];
getChildren(data, result, pid);
return result;
};
console.log(arrayToTree(arr, -1));
递归查找其实不是一种好的方式,因为递归性能比较差。
方法二:我们通过Map实现
主要思路是先把数据转成Map去存储,之后遍历的同时借助对象的引用,直接从Map找对应的数据做存储
function arrayToTree(items) {
const result = []; // 存放结果集
const itemMap = {}; //
// 先转成map存储
for (const item of items) {
itemMap[item.id] = { ...item, children: [] };
}
for (const item of items) {
const id = item.id;
const pid = item.pid;
const treeItem = itemMap[id];
if (pid === -1) {
result.push(treeItem);
} else {
if (!itemMap[pid]) {
itemMap[pid] = {
children: [],
};
}
itemMap[pid].children.push(treeItem);
}
}
return result;
}
方式三:性能最优
主要思路也是先把数据转成Map去存储,之后遍历的同时借助对象的引用,直接从Map找对应的数据做存储。不同点在遍历的时候即做Map存储,有找对应关系。性能会更好。
function arrayToTree(items) {
const result = []; // 存放结果集
const itemMap = {}; //
// 遍历数组
for (const item of items) {
const id = item.id;
const pid = item.pid;
// 如果对应的 map 对象里边没有 我们给一个初始值
if (!itemMap[id]) {
itemMap[id] = {
children: [],
};
}
// 我们将数值给到 map 数据
itemMap[id] = {
...item,
children: itemMap[id]["children"],
};
// 我们拿到 map 中对应的数据
const treeItem = itemMap[id];
// 如果没有父级
if (pid === -1) {
result.push(treeItem);
} else {
if (!itemMap[pid]) {
itemMap[pid] = {
children: [],
};
}
// 如果有父级,我们直接给父级里边存放
itemMap[pid].children.push(treeItem);
}
}
return result;
}
这里我从网上找了一个 github 上的插件:
里边的实现方式如下:
var property = require('nested-property');
var keyBy = require('lodash.keyby');
function createTree(array, rootNodes, customID, childrenProperty) {
var tree = [];
for (var rootNode in rootNodes) {
var node = rootNodes[rootNode];
var childNode = array[node[customID]];
if (!node && !rootNodes.hasOwnProperty(rootNode)) {
continue;
}
if (childNode) {
node[childrenProperty] = createTree(
array,
childNode,
customID,
childrenProperty
);
}
tree.push(node);
}
return tree;
}
function groupByParents(array, options) {
var arrayByID = keyBy(array, options.customID);
return array.reduce(function(prev, item) {
var parentID = property.get(item, options.parentProperty);
if (!parentID || !arrayByID.hasOwnProperty(parentID)) {
parentID = options.rootID;
}
if (parentID && prev.hasOwnProperty(parentID)) {
prev[parentID].push(item);
return prev;
}
prev[parentID] = [item];
return prev;
}, {});
}
function isObject(o) {
return Object.prototype.toString.call(o) === '[object Object]';
}
function deepClone(data) {
if (Array.isArray(data)) {
return data.map(deepClone);
} else if (isObject(data)) {
return Object.keys(data).reduce(function(o, k) {
o[k] = deepClone(data[k]);
return o;
}, {});
} else {
return data;
}
}
/**
* arrayToTree
* Convert a plain array of nodes (with pointers to parent nodes) to a nested
* data structure
*
* @name arrayToTree
* @function
*
* @param {Array} data An array of data
* @param {Object} options An object containing the following fields:
*
* - `parentProperty` (String): A name of a property where a link to
* a parent node could be found. Default: 'parent_id'
* - `customID` (String): An unique node identifier. Default: 'id'
* - `childrenProperty` (String): A name of a property where children nodes
* are going to be stored. Default: 'children'.
*
* @return {Array} Result of transformation
*/
module.exports = function arrayToTree(data, options) {
options = Object.assign(
{
parentProperty: 'parent_id',
childrenProperty: 'children',
customID: 'id',
rootID: '0'
},
options
);
if (!Array.isArray(data)) {
throw new TypeError('Expected an array but got an invalid argument');
}
var grouped = groupByParents(deepClone(data), options);
return createTree(
grouped,
grouped[options.rootID],
options.customID,
options.childrenProperty
);
}