1. 程式人生 > >樹形多級選單資料來源巢狀結構與扁平結構互轉

樹形多級選單資料來源巢狀結構與扁平結構互轉

1.前言

在日常開發中,往往會有這樣的需求:根據後端返回的資料,動態渲染出一顆多級導航選單樹,類似於計算機中資源管理器的樣子。如下圖所示:

要實現這樣的需求,其實不難,只是對後端返回的資料來源有要求,如果後端返回的資料能夠很清楚的表現出節點與節點之間的層級關係,那麼前端實現起來就易如反掌。

2.資料來源格式

一般來說,要想動態的渲染出一個樹形選單,如下所示的資料來源格式對前端開發人員來說是十分友好的。

var nodes = [
    {
    name: "父節點1",
    children: [
      {
        name: "子節點11",
        children:[
          {
            name: "葉子節點111",
            children:[]
          },
          {
            name: "葉子節點112",
            children:[]
          },
          {
            name: "葉子節點113",
            children:[]
          },
          {
            name: "葉子節點114",
            children:[]
          }
        ]
      },
      {
        name: "子節點12",
        children:[
          {
            name: "葉子節點121",
            children:[]
          },
          {
            name: "葉子節點122",
            children:[]
          },
          {
            name: "葉子節點123",
            children:[]
          },
          {
            name: "葉子節點124",
            children:[]
          }
        ]
      },
      {
        name: "子節點13",
        children:[]
      }
    ]
  },
  {
    name: "父節點2",
    children: [
      {
        name: "子節點21",
        children:[
          {
            name: "葉子節點211",
            children:[]
          },
          {
            name: "葉子節點212",
            children:[]
          },
          {
            name: "葉子節點213",
            children:[]
          },
          {
            name: "葉子節點214",
            children:[]
          }
        ]
      },
      {
        name: "子節點22",
        children:[
          {
            name: "葉子節點221",
            children:[]
          },
          {
            name: "葉子節點222",
            children:[]
          },
          {
            name: "葉子節點223",
            children:[]
          },
          {
            name: "葉子節點224",
            children:[]
          }
        ]
      },
      {
        name: "子節點23",
        children:[
          {
            name: "葉子節點231",
            children:[]
          },
          {
            name: "葉子節點232",
            children:[]
          },
          {
            name: "葉子節點233",
            children:[]
          },
          {
            name: "葉子節點234",
            children:[]
          }
        ]
      }
    ]
  },
  {
    name: "父節點3",
    children:[]
  },
];

後端返回這樣的資料來源格式,節點之間的層級關係一目瞭然,前端人員拿到資料,只需進行遞迴遍歷,並判斷children.length是否等於0,等於0表明當前節點已為葉子節點,停止遍歷即可。在上一篇博文vue+element UI以元件遞迴方式實現多級導航選單中,動態渲染多級導航選單,也是推薦使用這種資料來源格式的。

3.問題痛點

雖然前端人員想法是好的,但是在後端,這些資料通常是儲存在關係型資料庫中,後端開發將資料從資料庫中取出來返回給前端的資料往往這樣子的:

const nodes =[
  { id:1,   pid:0,  name:"父節點1"     },           
  { id:11,  pid:1,  name:"父節點11"    },
  { id:111, pid:11, name:"葉子節點111" },
  { id:112, pid:11, name:"葉子節點112" },
  { id:113, pid:11, name:"葉子節點113" },
  { id:114, pid:11, name:"葉子節點114" },
  { id:12,  pid:1,  name:"父節點12"    },
  { id:121, pid:12, name:"葉子節點121" },
  { id:122, pid:12, name:"葉子節點122" },
  { id:123, pid:12, name:"葉子節點123" },
  { id:124, pid:12, name:"葉子節點124" },
  { id:13,  pid:1,  name:"父節點13"    },
  { id:2,   pid:0,  name:"父節點2"     },
  { id:21,  pid:2,  name:"父節點21"    },
  { id:211, pid:21, name:"葉子節點211" },
  { id:212, pid:21, name:"葉子節點212" },
  { id:213, pid:21, name:"葉子節點213" },
  { id:214, pid:21, name:"葉子節點214" },
  { id:22,  pid:2,  name:"父節點22"    },
  { id:221, pid:22, name:"葉子節點221" },
  { id:222, pid:22, name:"葉子節點222" },
  { id:223, pid:22, name:"葉子節點223" },
  { id:224, pid:22, name:"葉子節點224" },
  { id:23,  pid:2,  name:"父節點23"    },
  { id:231, pid:23, name:"葉子節點231" },
  { id:232, pid:23, name:"葉子節點232" },
  { id:233, pid:23, name:"葉子節點233" },
  { id:234, pid:23, name:"葉子節點234" },
  { id:3,   pid:0,  name:"父節點3"     }
];

其中,層級關係是通過idpid提現的,id為節點的序號,pid為該節點的父節點序號,如果為頂級節點,則其pid為0。

其實,這樣的資料格式對前端來說,也不是不能用,就是沒有上面那種格式用起來方便,所以,有時候前端同學就得去跪舔後端人員:

“後端大哥,能不能給我返回像這樣子的資料呀?”

如果前端同學是個妹子還好,撒個嬌就完事了,可如果是個漢子,後端大哥往往會迴應你:

“滾,給你返回資料就不錯了,還挑三揀四,想要啥樣子的自己造去。”

4.解決方案

為了防止被後端同學懟(其實以上對話是博主親身經歷,摔~~~),我們前端人員果斷自己動手,豐衣足食。

為了解決上述問題,博主自己寫了兩個方法,來實現兩種資料來源格式互相轉化。我們姑且稱理想資料格式為“巢狀型格式”,後端返回的格式為“扁平型格式”,那麼兩個互轉方法程式碼如下:

/**
 * 扁平型格式轉巢狀型格式
 * @param {Array} data 
 */
function FlatToNested(data) { 
  var res = [],
    tmpMap = [];
  for (let i = 0; i < data.length; i++) {
    tmpMap[data[i]['id']] = data[i];
    if (tmpMap[data[i]['pid']] && data[i]['id'] != data[i]['pid']) {
      if (!tmpMap[data[i]['pid']]['children'])
        tmpMap[data[i]['pid']]['children'] = [];
      data[i]['name'] = data[i]['name'];
      tmpMap[data[i]['pid']]['children'].push(data[i]);
    } else {
      data[i]['name'] = data[i]['name'];
      res.push(data[i]);
    }
  }
  return res;
}
/**
 * 巢狀型格式轉扁平型格式
 * @param {Array} data 
 */
function NestedToFlat(data) { 
  var res = []
  for (var i = 0; i < data.length; i++) {
    res.push({
      id: data[i].id,
      name: data[i].name,
      pid: 0
    })
    if (data[i].children) {
      res = res.concat(NestedToFlat(data[i].children, data[i].id));
    }
  }
  return res;
}

5.小結

有了這兩個方法,我們前端人員再也不用去跪舔後端,要啥有啥,美滋滋!

(完)