Tree和array之間相互轉換

(一)前言
對於大部分網站需求,我們經常會遇到一種需求,將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')));

從上面結果我們可以看到,目前數據結構變爲如下了。

array列表(三)查詢子元素

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. 方案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字段。

  1. 方案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));

和上面一樣,我們可以看到對於數據已經被修改。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章