樹形多級菜單數據源嵌套結構與扁平結構互轉

1.前言

在平常開發中,每每會有這樣的需求:根據後端返回的數據,動態渲染出一顆多級導航菜單樹,相似於計算機中資源管理器的樣子。以下圖所示:
javascript

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

2.數據源格式

通常來講,要想動態的渲染出一個樹形菜單,以下所示的數據源格式對前端開發人員來講是十分友好的。前端

var data = [
    {
    name: "父節點1",
    children: [
      {
        name: "子節點11",
        children:[
          {
            name: "葉子節點111",
            children:[]
          },
          {
            name: "葉子節點112",
            children:[]
          },
          {
            name: "葉子節點113",
            children:[]
          },
          {
            name: "葉子節點114",
            children:[]
          }
        ]
      }
     //...
    ]
  },
];

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

3.問題痛點

雖然前端人員想法是好的,可是在後端,這些數據一般是存儲在關係型數據庫中,後端開發將數據從數據庫中取出來返回給前端的數據每每這樣子的:java

const data =[
  { 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。node

其實,這樣的數據格式對前端來講,也不是不能用,就是沒有上面那種格式用起來方便,因此,有時候前端同窗就得去跪舔後端人員:數據庫

「後端大哥,能不能給我返回像這樣子的數據呀?」後端

若是前端同窗是個妹子還好,撒個嬌就完事了,可若是是個漢子,後端大哥每每會迴應你:數組

「滾,給你返回數據就不錯了,還挑三揀四,想要啥樣子的本身造去。」數據結構

4.解決方案

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

爲了解決上述問題,博主本身寫了兩個方法,來實現兩種數據源格式互相轉化。咱們姑且稱理想數據格式爲「嵌套型格式」,後端返回的格式爲「扁平型格式」,那麼兩個互轉方法代碼以下:

/**
 * 將一個普通的節點數組(帶有指向父節點的指針)轉換爲嵌套的數據結構。
 * @param {*} data  一組數據
 * @param {*} option 包含如下字段的對象:
 *      parentProperty(String):能夠找到父節點連接的屬性的名稱。默認值:'pid'。
 *      childrenProperty(String):將存儲子節點的屬性的名稱。默認值:'children'。
 *      idProperty(String):惟一的節點標識符。默認值:'id'。
 *      nameProperty(String):節點的名稱。默認值:'name'。
 */
function FlatToNested(data, option) {
  option = option || {};
  let idProperty = option.idProperty || 'id';
  let parentProperty = option.parentProperty || 'pid';
  let childrenProperty = option.childrenProperty || 'children';
  let res = [],
    tmpMap = [];
  for (let i = 0; i < data.length; i++) {
    tmpMap[data[i][idProperty]] = data[i];
    if (tmpMap[data[i][parentProperty]] && data[i][idProperty] != data[i][parentProperty]) {
      if (!tmpMap[data[i][parentProperty]][childrenProperty])
        tmpMap[data[i][parentProperty]][childrenProperty] = [];
      tmpMap[data[i][parentProperty]][childrenProperty].push(data[i]);
    } else {
      res.push(data[i]);
    }
  }
  return res;
}
/**
 * 嵌套型格式轉扁平型格式
 * @param {Array} data 
 */
function NestedToFlat(data,pid) { 
  var res = []
  for (var i = 0; i < data.length; i++) {
    res.push({
      id: data[i].id,
      name: data[i].name,
      pid: pid || 0
    })
    if (data[i].children) {
      res = res.concat(NestedToFlat(data[i].children, data[i].id));
    }
  }
  return res;
}

5.使用方法

5.1 」扁平型格式「轉」嵌套型格式「

若是在實際開發中後端返回的數據跟以下實例數據中字段一致的狀況下:

//示例數據
const data =[
  { 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" }
  //...
];

那麼直接調用方法

FlatToNested(data)

便可將扁平化數據轉化成嵌套型數據。

若是返回的數據和示例數據的字段不一致,那麼您也無需更改源代碼,方法提供了可配置選項,以下所示:

例如,您收到這樣的數據:

const data =[
    { _id:1,   parentID:0,  text:"父節點1"     },           
    { _id:11,  parentID:1,  text:"父節點11"    },
    { _id:111, parentID:11, text:"葉子節點111" },
    { _id:112, parentID:11, text:"葉子節點112" },
    { _id:113, parentID:11, text:"葉子節點113" },
    { _id:114, parentID:11, text:"葉子節點114" },
    { _id:12,  parentID:1,  text:"父節點12"    },
    { _id:121, parentID:12, text:"葉子節點121" },
    { _id:122, parentID:12, text:"葉子節點122" }
    //...
  ];

那麼,您能夠這樣調用函數:

FlatToNested(nodes,{
    idProperty:'_id',            //惟一的節點標識符。
    nameProperty:'text',         //節點的名稱。
    parentProperty:'parentID',  //能夠找到父節點連接的屬性的名稱。
    childrenProperty:'son'      //將存儲子節點的屬性的名稱。
})

5.2 」嵌套型格式「轉」扁平型格式「

假若有以下嵌套型格式的數據:

let data = [
  {
      id:1,
      name:'根節點',
      children:[
        {
          id:11,
          name: '父節點11',
          children: [
            {
              id:111,
              name:'父節點111',
            },
            {
              id:112,
              name:'父節點112',
            }
          ]
        },
        {
          id:12,
          name:'父節點12',
        }
      ]
  }
]

那麼直接調用方法

NestedToFlat(data)

便可將嵌套型數據轉化成扁平型數據:

[
  { id: 1, name: '根節點', pid: 0 },
  { id: 11, name: '父節點11', pid: 1 },
  { id: 111, name: '父節點111', pid: 11 },
  { id: 112, name: '父節點112', pid: 11 },
  { id: 12, name: '父節點12', pid: 1 }
]

6.小結

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

(完)

相關文章
相關標籤/搜索