# 树形相关方法

​ 树形数组在很多情况下都会使用,下面将树形相关方法汇总起来。

# 树形数组层级数据

const areaTreeData = [
    {
        "id": "35060000",
        "areaName": "漳州市",
        "children": [
            {
                "id": "35062400",
                "areaName": "诏安县",
                "children": [
                    {
                        "id": "35062401",
                        "areaName": "太平镇",
                    },
                    {
                        "id": "35062402",
                        "areaName": "金星乡",
                    },
                    {
                        "id": "35062403",
                        "areaName": "南诏镇",
                    },
                    {
                        "id": "35062404",
                        "areaName": "霞葛镇",
                    }
                ],
                "leaf": false
            },
            {
                "id": "35062300",
                "areaName": "云霄县",
                "children": [
                    {
                        "id": "35062301",
                        "areaName": "列屿镇",
                    },
                    {
                        "id": "35062302",
                        "areaName": "陈岱镇",
                    },
                    {
                        "id": "35062303",
                        "areaName": "云陵镇",
                    },
                    {
                        "id": "35062304",
                        "areaName": "火田镇",
                    }
                ],
            }
        ],
    }
]

# 树形数组一维数据

const areaArrData = [
    {
      "id": "35060000",
      "parentId": "0",
      "areaName": "漳州市"
    },
    {
      "id": "35062400",
      "parentId": "35060000",
      "areaName": "诏安县"
    },
    {
      "id": "35062401",
      "parentId": "35062400",
      "areaName": "太平镇"
    },
    {
      "id": "35062402",
      "parentId": "35062400",
      "areaName": "金星乡"
    },
    {
      "id": "35062403",
      "parentId": "35062400",
      "areaName": "南诏镇"
    },
    {
      "id": "35062404",
      "parentId": "35062400",
      "areaName": "霞葛镇"
    },
    {
      "id": "35062300",
      "parentId": "35060000",
      "areaName": "云霄县"
    },
    {
      "id": "35062301",
      "parentId": "35062300",
      "areaName": "列屿镇"
    },
    {
      "id": "35062302",
      "parentId": "35062300",
      "areaName": "陈岱镇"
    },
    {
      "id": "35062303",
      "parentId": "35062300",
      "areaName": "云陵镇"
    },
    {
      "id": "35062304",
      "parentId": "35062300",
      "areaName": "火田镇"
    }
  ]

# 方法汇总

# 方法一:将一维树形数据转化为层级树形数据

  1. **listToTree **简洁版为
export function listToTree(data, parentId) {
  var tree = [];
  var temp;
  // 遍历数组
  for (var i = 0; i < data.length; i++) {
    // 如果这个元素的 父id 是要找的
    if (data[i].parentId === parentId) {
      var obj = data[i];
      // 查找这个元素的子元素
      temp = listToTree(data, obj.id)
      if (temp.length > 0) {
        obj.children = temp
      }
      tree.push(obj);
    }
  }
  return tree;
}
  1. **listToTree **升级版本,使之变成可配置并且每项会新增字段
export function listToTree(data, config, parentObj) {
  const defaultOptions = {
    idField: "id", //唯一标识字段名
    parentIdField: "parentId", //父节点唯一标识字段名
    childrenField: "children", //子节点标识字段名
    firstId: "0", // 根节点值
    labelField: "label", //label字段名
    labelArrField: "labelArr", //给对象新增的中文数组字段名
    idArrField: "idArr", //给对象新增的id数组字段名
    targetArrField: "", //目标字段数组  多个可以用 , 分隔开
    levelField: "level", //给对象新增的层级字段名
    level: 0, // 给根目录配置的层级
    leafField: "leaf" //叶子节点标识字段名
  }
  Object.assign(defaultOptions, config)

  if (!parentObj) {
    parentObj = {
      [defaultOptions.idField]: defaultOptions.firstId,
      [defaultOptions.levelField]: defaultOptions.level,
      [defaultOptions.labelArrField]: [],
      [defaultOptions.idArrField]: [],
    }
  }

  var tree = [];
  var temp;
  // 遍历数组
  for (var i = 0; i < data.length; i++) {
    // 如果这个元素的 父id 是要找的
    if (data[i][defaultOptions.parentIdField] === parentObj[defaultOptions.idField]) {
      var obj = data[i];
      // 给元素添加层级标志
      obj[defaultOptions.levelField] = parentObj[defaultOptions.levelField] + 1
      // 给元素添加 字段数组
      obj[defaultOptions.labelArrField] = parentObj[defaultOptions.labelArrField].concat(obj[defaultOptions.labelField])
      // 给元素添加 id数组
      obj[defaultOptions.idArrField] = parentObj[defaultOptions.idArrField].concat(obj[defaultOptions.idField])
 
      // 查找这个元素的子元素
      temp = listToTree(data, config, obj)
      if (temp.length > 0) {
        obj[defaultOptions.childrenField] = temp
        obj[defaultOptions.leafField] = false
      } else {
        // 没有子元素 说明是叶子节点
        obj[defaultOptions.leafField] = true
      }
      tree.push(obj);
    }
  }
  return tree;
}

# 方法二:树形层级数据,根据ID查询父级元素路径

  1. getPath( reduce方法) 初级版
const getPath = (arr, id) => {
  return arr.reduce((total, item) => {
    if (item.id == id) {
      total.push(item)
    } else {
      if (item.children && item.children.length) {
        let path = getPath(item.children, id)
        if (path && path.length) {
          total.unshift(item, ...path)
        }
      }
    }
    return total
  }, [])
}
  1. getPath( reduce方法) 升级成可配置
const getPath = (arr, targetValue, config) => {
  const defaultConfig = {
      targetField: "id", // 查找目标值的字段名
      childrenField: "children" //子节点标识字段名
  }
  Object.assign(defaultConfig, config)
  return arr.reduce((total, item) => {
    if (item[defaultConfig.targetField] === targetValue) {
      total.push(item)
    } else {
      if (item[defaultConfig.childrenField] && item[defaultConfig.childrenField].length) {
        const path = getPath(item[defaultConfig.childrenField], targetValue, config)
        if (path && path.length) {
          total.unshift(item, ...path)
        }
      } 
    }
    
    return total
  }, [])
}
  1. getPath( for方法) 初级版
const getPath = (arr, id) => {
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i]
    if (item.id == id) {
      return [item]
    } else if (item.children && item.children.length) {
      const path = getPath(item.children, id)
      if (path && path.length) {
        path.unshift(item)
        return path
      }
    }
  }
}
  1. getPath( for方法) 升级版
const getPath = (arr, targetValue, config ) => {
  const defaultConfig = {
      targetField: "id", // 查找目标值的字段名
      childrenField: "children" //子节点标识字段名
  }
  Object.assign(defaultConfig, config)
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i]
    if (item[defaultConfig.targetField] === targetValue) {
      return [item]
    } else if (item[defaultConfig.childrenField] && item[defaultConfig.childrenField].length) {
      const path = getPath(item[defaultConfig.childrenField], targetValue, config)
      if (path && path.length) {
        path.unshift(item)
        return path
      }
    }
  }
}

# 方法三:树形层级数据转化为以为数组结构

  1. 获取树形数据所有叶子节点
/**
 * @description: 获取所有叶子节点
 * @return {*}
 * @author: syx
 */
export function treeToList(treeArr) {
  var r = []
  if (Array.isArray(treeArr)) {
    for (var i = 0, l = treeArr.length; i < l; i++) {
      const item = treeArr[i]
      if (Array.isArray(item.children) && item.children.length > 0) {
        // 若存在children则递归调用,把数据拼接到新数组中,并且删除该children
        r = r.concat(treeToList(item.children))
      } else {
        r.push(item) // 取每项数据放入一个新数组
      }
      delete item.children
    }
  }
  return r
}
  1. 获取树形数据所有元素
/**
 * @description: 获取所有叶子节点
 * @return {*}
 * @author: syx
 */
export function treeToList(treeArr) {
  var r = []
  if (Array.isArray(treeArr)) {
    for (var i = 0, l = treeArr.length; i < l; i++) {
      const item = treeArr[i]
      if (Array.isArray(item.children) && item.children.length > 0) {
        r.push(item)
        // 若存在children则递归调用,把数据拼接到新数组中,并且删除该children
        r = r.concat(treeToList(item.children))
      } else {
        r.push(item) // 取每项数据放入一个新数组
      }
      delete item.children
    }
  }
  return r
}
  1. 获取树形数据所有元素并且添加pid字段
/**
 * @description: 获取树形数据所有元素并且添加pid字段
 * @return {*}
 * @author: syx
 */
export function treeToList(treeArr, parentId = "0") {
  var r = []
  if (Array.isArray(treeArr)) {
    for (var i = 0, l = treeArr.length; i < l; i++) {
      const item = treeArr[i]
      item.pid = parentId
      if (Array.isArray(item.children) && item.children.length > 0) {
        r.push(item)
        // 若存在children则递归调用,把数据拼接到新数组中,并且删除该children
        r = r.concat(treeToList(item.children, item.id))
      } else {
        r.push(item) // 取每项数据放入一个新数组
      }
      delete item.children
    }
  }
  return r
}
  1. 获取树形数据所有元素并且添加level层级字段
/**
 * @description: 获取树形数据所有元素并且添加level层级字段
 * @return {*}
 * @author: syx
 */
export function treeToList(treeArr, level = 0) {
  var r = []
  if (Array.isArray(treeArr)) {
    for (var i = 0, l = treeArr.length; i < l; i++) {
      const item = treeArr[i]
      item.level = level + 1
      if (Array.isArray(item.children) && item.children.length > 0) {
        r.push(item)
        // 若存在children则递归调用,把数据拼接到新数组中,并且删除该children
        r = r.concat(treeToList(item.children, item.level))
      } else {
        r.push(item) // 取每项数据放入一个新数组
      }
      delete item.children
    }
  }
  return r
}
  1. 升级成可配置树形转数组方法
/**
 * @description: 可配置树形转一维数组
 * @return {*}
 * @author: syx
 */
export function treeToList(treeArr, config, rootObj) {
  const defaultOptions = {
    idField: "id", //唯一标识字段名
    parentIdField: "parentId", //父节点唯一标识字段名
    childrenField: "children", //子节点标识字段名
    firstId: "0", // 根节点值
    labelField: "label", //label字段名
    labelArrField: "labelArr", //给对象新增的中文数组字段名
    idArrField: "idArr", //给对象新增的id数组字段名
    targetArrField: "", //目标字段数组  多个可以用 , 分隔开
    levelField: "level", //给对象新增的层级字段名
    level: 0, // 给根目录配置的层级
    leafField: "leaf" //叶子节点标识字段名
  }
  Object.assign(defaultOptions, config)

  // 没值说明是根目录
  if (!rootObj) {
    rootObj = {
      [defaultOptions.idField]: defaultOptions.firstId, //自身id
      [defaultOptions.levelField]: defaultOptions.level, // 自身层级
      [defaultOptions.labelArrField]: [], // 自身labelArr
      [defaultOptions.idArrField]: [], // 自身labelArr
    }
  }
  var r = []
  if (Array.isArray(treeArr)) {
    for (var i = 0, l = treeArr.length; i < l; i++) {
      const item = treeArr[i]
      // 层级
      item[defaultOptions.levelField] = rootObj[defaultOptions.levelField] + 1
      // 父元素id
      item[defaultOptions.parentIdField] = rootObj[defaultOptions.idField]
      // label数组
      item[defaultOptions.labelArrField] = rootObj[defaultOptions.labelArrField].concat([item[defaultOptions.labelField]])
      // id数组
      item[defaultOptions.idArrField] = rootObj[defaultOptions.idArrField].concat([item[defaultOptions.idField]])

      if (Array.isArray(item.children) && item.children.length > 0) {
        // 不是叶子节点
        item[defaultOptions.leafField] = false
        r.push(item)
        // 若存在children则递归调用,把数据拼接到新数组中,并且删除该children
        r = r.concat(treeToList(item.children,config, item))
      } else {
        // 叶子节点
        item[defaultOptions.leafField] = true
        r.push(item) // 取每项数据放入一个新数组
      }
      delete item.children
    }
  }
  return r
}

# 方法四:树形层级数据过滤部分数据

  1. 树形数据过滤id为35062300的数据
// 定义递归方法,接收一个数组
function deepFilter(list, exceptId) {
  // 使用filter 过滤当前层的数组
  return list.filter(item => {

    // filter其实也是遍历
    // 把当前遍历的节点的children 也调用一次 deepFilter 函数,返回过滤后的数组重新赋值
    if (item.children&&item.children.length > 0) {
      item.children = deepFilter(item.children, exceptId)
    }

    // 最后判断当前节点是否符合过滤要求
    return item.id !== exceptId
  })
}
// 过滤掉id为35062300 的数据 子元素也会 过滤
deepFilter(areaTreeData, "35062300")
  1. 升级成可灵活使用的函数
// 定义递归方法,接收一个数组
function deepFilter(list, callback) {

  // 使用filter 过滤当前层的数组
  return list.filter(item => {

    // filter其实也是遍历
    // 把当前遍历的节点的children 也调用一次 deepFilter 函数,返回过滤后的数组重新赋值
    if (item.children&&item.children.length > 0) {
      item.children = deepFilter(item.children, callback)
    }

    // 最后判断当前节点是否符合过滤要求
    return callback&&callback(item)
  })
}
deepFilter(areaTreeData, (item) => {return item.id !== "35062300"})
  1. 可配置children字段
// 定义递归方法,接收一个数组
function deepFilter(list, callback, childrenField="children") {

  // 使用filter 过滤当前层的数组
  return list.filter(item => {

    // filter其实也是遍历
    // 把当前遍历的节点的children 也调用一次 deepFilter 函数,返回过滤后的数组重新赋值
    if (item[childrenField]&&item[childrenField].length > 0) {
      item[childrenField] = deepFilter(item[childrenField], callback)
    }

    // 最后判断当前节点是否符合过滤要求
    return callback&&callback(item)
  })
}
deepFilter(areaTreeData, (item) => {return item.id !== "35062300"})

# 总结

​ 树形转一维数组,一维数组转树形,上述方法希望能帮到大家。

阅读原文 (opens new window)