(一)前言
對於大部分網站需求,我們經常會遇到一種需求,將list轉爲tree結構,或者將tree轉爲list結構,或者我們在tree結構中查找對於的數據,返回,或者返回頂層到查找級別的list。
(二)將list轉爲tree
我們一般會在後臺數據庫設計時候,當我們設計省市縣聯動時候,一般會在數據庫設計唯一索引id,和pid字段,pid關聯父級別id,這樣當查詢出來的數據很可能是類似下面的結構
const data = [
{ id: 1, pid: 0, name: 'no.1' },
{ id: 2, pid: 1, name: 'no.2' },
{ id: 3, pid: 1, name: 'no.3' },
{ id: 4, pid: 1, name: 'no.4' },
{ id: 5, pid: 1, name: 'no.5' },
{ id: 6, pid: 2, name: 'no.6' },
{ id: 7, pid: 2, name: 'no.7' },
{ id: 8, pid: 2, name: 'no.8' },
{ id: 9, pid: 3, name: 'no.9' },
{ id: 10, pid: 3, name: 'no.10' },
{ id: 11, pid: 3, name: 'no.11' },
{ id: 12, pid: 4, name: 'no.12' },
{ id: 13, pid: 4, name: 'no.13' },
{ id: 14, pid: 13, name: 'no.14' },
];
但是實際上大部分UI展示需要生成tree形結構,方便UI暫時,這時候我們應該怎麼辦呢,你可以寫一個arrayToTree方法,類似如下
function arrayToTree(list, pid = 0) {
return list.filter(item => item.pid === pid).map(item => ({
...item,
children: arrayToTree(list, item.id),
}));
}
// 調用
console.log(arrayToTree(data));
那麼調用和輸出結構就如下
我們可以看到已經成功將list轉爲tree。
(三)將tree轉爲list
我們直接使用上面生成的tree結構,如下,來將它扁平化成一維數組
const data = [
{
id: 1,
pid: 0,
name: 'no.1',
children: [
{
id: 2,
pid: 1,
name: 'no.2',
children: [
{ id: 6, pid: 2, name: 'no.6' },
{ id: 7, pid: 2, name: 'no.7' },
{ id: 8, pid: 2, name: 'no.8' },
],
},
{
id: 3,
pid: 1,
name: 'no.3',
children: [
{ id: 9, pid: 3, name: 'no.9' },
{ id: 10, pid: 3, name: 'no.10' },
{ id: 11, pid: 3, name: 'no.11' },
],
},
{
id: 4,
pid: 1,
name: 'no.4',
children: [
{ id: 12, pid: 4, name: 'no.12' },
{
id: 13,
pid: 4,
name: 'no.13',
children: [
{ id: 14, pid: 13, name: 'no.14' },
],
},
],
},
{ id: 5, pid: 1, name: 'no.5' },
],
},
];
現在我們來寫treeToArray方法,因爲生成list會根據當前層級對於扁平化,所以需要對list使用sort作依次排序,那麼我們就需要一個compareProps的方法來生成序列化的列表
function compareProps(key) {
return (obj1, obj2) => {
const val1 = obj1[key];
const val2 = obj2[key];
if (val1 < val2) {
return -1;
}
if (val1 > val2) {
return 1;
}
return 0;
};
}
function treeToArray(list, newArr = []) {
list.forEach((item) => {
const { children } = item;
if (children) {
delete item.children;
if (children.length) {
newArr.push(item);
return treeToArray(children, newArr);
}
}
newArr.push(item);
});
return newArr;
}
console.log(treeToArray(data).sort(compareProps('id')));
從上面結果我們可以看到,目前數據結構變爲如下了。
(三)查詢子元素
list如果需要使用id查詢一個列表中對象,簡單就提供find,findIndex api,所以這裏不做介紹,我們來總結在如果知道一個id,如何在tree得到其所在層級或者之前上級到頂層的關係。
這裏我實現來一個getTreePathList方法,我先上代碼,再來爲大家介紹相關參數
/**
* 遞歸查詢tree path
* @param {array} list 數組
* @param {string} value 比對值
* @param {object} options 配置
* {
* {String} equalKey 比對key
* {String} returnKey 返回key
* {Bool} returnIndex 是否返回index
* {Bool} returnOnlyLast 只返回最後一個對象
* }
* @return {*} 從頂層到當前層級
*/
// 這裏list是需要查詢的願tree,
// value是查詢的值,
// 第三個參數爲配置項,需要注意就是如果returnOnlyLast爲true,只會返回搜索元素當前層的object
function getTreePathList(list, value, {
equalKey = 'name',
returnKey = 'uid',
returnIndex = false,
returnOnlyLast = false,
} = {}) {
for (let i = 0; i < list.length; i += 1) {
const { children = [], [equalKey]: name, [returnKey]: uid } = list[i];
const returnMap = (returnIndex || returnOnlyLast) ? {
...list[i],
index: i,
} : uid;
if (name === value) {
if (returnOnlyLast) return returnMap;
return [returnMap];
}
if (children && children.length) {
const res = getTreePathList(children, value, {
equalKey,
returnKey,
returnIndex,
returnOnlyLast,
});
if (res) {
if (returnOnlyLast) return res;
return [returnMap, res].flat() || [];
}
}
}
}
那麼我們需要父級的就這樣使用
console.log(getTreePathList(data, 14, {
equalKey: 'id',
returnIndex: true,
}));
這裏我們可以看到,第一個參數是列表list,第二個是查找的值,第三個是個對象提供equalKey, returnKey, returnIndex, returnOnlyLast。首先equalKey是比對的key,returnKey當returnIndex/returnOnlyLast爲false時候,返回的key,類似這種[name1, name2, name3, …], 最後分別是returnIndex,會將每次循環的index返回,這個可能在做搜索滾動距離需要,returnOnlyLast就是表示這時候 只會返回找到的對象。
(四)修改tree結構字段
當我們後端返回tree結構時候,常規場景,會需要將數據修改爲ui框架需要的數據結構,這時候你就需要一個方法來處理數據
- 方案1
// 迭代器
function iterator(arr, cb, isFirst = true) {
for (let i = 0; i < arr.length; i += 1) {
/* eslint-disable no-continue */
// 這是做狀態過濾
// if (arr[i].status !== 1) {
// arr.splice(i, 1);
// i -= 1;
// continue;
// }
cb.call(arr[i], arr[i], arr.length === 1 && isFirst);
}
}
// 處理每一項
function changeItem(item, isFirst) {
// 更改字段名稱
item.isFirst = isFirst;
item.key = item.id;
item.title = item.name;
item.tags_children = item.children || [];
if (item.children) {
iterator(item.children, changeItem, false);
delete item.children;
}
}
terator(data, changeItem);
console.log(data);
我們可以看到原始data裏面children已經改爲tags_children,新增來key,title, isFirst字段。
- 方案2 不在外面包裝迭代器
function changeTreeKey(list, isFirst = true) {
return list.map((item) => {
item.isFirst = isFirst;
item.key = item.id;
item.title = item.name;
item.tags_children = item.children || [];
if (item.children) {
delete item.children;
changeTreeKey(item.tags_children, false);
}
return item;
});
}
console.log(changeTreeKey(data));
和上面一樣,我們可以看到對於數據已經被修改。