(JS 构造树工具方法) 数组转成树结构

方法说明

通过传入含有父子级关系的数组(含有父级key),生成树型结构,树型结构用途很广泛,但是构造的时候很麻烦,核心思路一般用方法的递归实现,下面提供了一个公共的工具方法(实现了基础树结构的生成及过滤数据的功能)。


一、array => tree 工具方法:方法中含有两个内置方法,显得很长,但为了方便copy都放到一起了,isNull 是判空方法,deepClone 是深度复制方法(避免更改到原来的数组对象)。本文最下面还有一个通过树型结构生成数组的工具方法(tree => array )
config传参说明:
  • labelField 是树节点显示的名称字段名,默认为 name
  • keyField 是节点key的字段名,默认为 id
  • pKeyField 是节点的父级key的字段名,默认为 pId
  • initNodeKey 是树的根节点值,一般需要传,不传则默认为字符串的0
  • maxHierarchy 限制最大层级, 为-1时无限制
  • hideConfig: {
  • openHide 是否开启隐藏节点的功能,默认为 true
  • field 是树节点的是否异常的标记字段名,默认为 isHide
  • hideValue 是指定哪些值需要隐藏(可以传多个),默认是 [1]
  • }
// 输入示例 id、pId、label是生成树的关键字段,同字段的值将会被覆盖,其他原有的字段将会被保留
[{ name: 'node1', id: '0', pId: ''}, { name: 'node2', id: '1', pId: '0'}]
// 结果示例
[
    {
        "name":"node1",
        "id":"0",
        "pId":"",
        "label":"node1",
        "children":[ // 子节点点
            {
                "name":"node2",
                "id":"1",
                "pId":"0",
                "label":"node2"
            }
        ]
    }
]
// 调用示例例
produceTree([{ name: 'node1', id: '0', pId: ''}, { name: 'node2', id: '1', pId: '0'}])
produceTree([{ name: 'node1', id: '0', pId: ''}, { name: 'node2', id: '1', pId: '0'}], 
				{initNodeKey: '0'} // initNodeKey 是树的根节点值,一般需要传,不传默认为'0' )
produceTree(
	[ // 含有父子级关系的数组 示例,id会被作为树的节点key,pId用来确认父子级关系
		{ name: 'node1', id: '0', pId: ''}, 
		{ name: 'node2', id: '1', pId: '0'}
	],
	 // config 传参,具体描述见下方,congig传参采用了解构方式,忽略任意一个参数都将取用默认值
	{ labelField: 'name', keyField: 'id', pKeyField: 'pId', initNodeKey: '0', maxHierarchy: 1}
)
/**
 * @function
 * @name produceTree
 * @description 方法描述:数组生成树形数据
 * @param {Array} data 数组
 * @param {Map} config 配置参数,有默认值
 * @example produceTree([{ name: 'node1', id: '0', pId: ''}, { name: 'node2', id: '1', pId: '0'}],{ labelField: 'name', keyField: 'id', pKeyField: 'pId', initNodeKey: '0'})
 * @returns {Array} - 数组生成树形数据
 */
function produceTree (data, config = {}) {
  // 默认配置解析
  var configOri = {
    labelField: 'name', // 名称字段名
    keyField: 'id', // key字段名
    pKeyField: 'pId', // 父级key字段名
    initNodeKey: '0', // 树顶级key值
    maxHierarchy: -1, // 限制最大层级深度, 为-1时无限制
    hideConfig: {  // 是否隐藏配置
      openHide: true, // 是否启用隐藏
      field: 'isHide', // 字段
      hideValue: ['1'] // 为什么值时要隐藏
    }
  }
  // 配置解析
  config.hideConfig = { ...configOri.hideConfig, ...config.hideConfig }
  var { labelField, keyField, pKeyField, initNodeKey, hideConfig } = { ...configOri, ...config }
  // 将要忽略的值转成map
  hideConfig.hideValueMap = {}
  hideConfig.hideValue.forEach(hF => {
    hideConfig.hideValueMap[hF] = hF
  })
  // 内置工具方法
  function isNull (obj) {
    if (obj === null || obj === undefined || obj === '') {
      return true
    }
    return false
  }
  // 深度复制
  function deepClone (source) {
    if (!source && typeof source !== 'object') {
      throw new Error('error arguments', 'shallowClone')
    }
    const targetObj = source.constructor === Array ? [] : {}
    for (const keys in source) {
      if (source.hasOwnProperty(keys)) {
        if (source[keys] && typeof source[keys] === 'object') {
          targetObj[keys] = source[keys].constructor === Array ? [] : {}
          targetObj[keys] = deepClone(source[keys])
        } else {
          targetObj[keys] = source[keys]
        }
      }
    }
    return targetObj
  }
  data = deepClone(data)
  var initNode
  var treeList = []
  // 预处理数据,并找到根节点
  data.forEach(row => {
    row.label = row[labelField]
    row.id = row[keyField]
    row.pId = row[pKeyField]
    if (!(hideConfig.openHide && !isNull(hideConfig.hideValueMap[row[hideConfig.field]]))) {
      if (row.id === initNodeKey) {
        initNode = row
        initNode.children = []
      } else {
        treeList.push(row)
      }
    }
  })
  if (isNull(initNode)) {
    console.log('根节点不存在!')
    return []
  }
  // 树的递归方法
  var treeRecursion = function (treeList, key, maxHierarchy) {
    var treeChildren = []
    treeList.forEach(row => {
      if (row.pId === key) {
      	if (maxHierarchy === 0) {
          return treeChildren
        }
        treeChildren.push(row)
        var children = treeRecursion(treeList, row.id, maxHierarchy - 1)
        if (children.length > 0) {
          row.children = children
        }
      }
    })
    return treeChildren
  }
  initNode.children = treeRecursion(treeList, initNodeKey, config.maxHierarchy)
  return [initNode]
}

二、tree => array 工具方法(通过树结构数据生成列表) ,这个方法可能比较少用,但有时候可能也挺需要的,调用方式注解上有详细描述。
/**
 * @function
 * @name treeProduceList
 * @description 方法描述:树结构数据生成列表
 * @param {Array} treeData 数组
 * @param {*} childrenField 子节点字段名, 默认为 children
 * @param {String} filterField 过滤字段名,如果需要筛选的话传入,否则可不传
 * @param {Array} filterValues 过滤字段值,多值用','隔开,如 '1,2,3' 这样子
 * @example treeProduceList([{"id":"1","label":"node1","children":[{"id":"2","pId":"1","label":"node2"}]}], 'children')
 * @returns {Array} - 数组生成树形数据
 */
export function treeProduceList (treeData, childrenField = 'children', filterField, filterValues) {
  function deepClone (source) {
    if (!source && typeof source !== 'object') {
      throw new Error('error arguments', 'shallowClone')
    }
    const targetObj = source.constructor === Array ? [] : {}
    for (const keys in source) {
      if (source.hasOwnProperty(keys)) {
        if (source[keys] && typeof source[keys] === 'object') {
          targetObj[keys] = source[keys].constructor === Array ? [] : {}
          targetObj[keys] = deepClone(source[keys])
        } else {
          targetObj[keys] = source[keys]
        }
      }
    }
    return targetObj
  }
  treeData = deepClone(treeData)
  var array = []
  treeData.forEach(item => {
    var children = item[childrenField]
    delete item[childrenField]
    if (filterValues) {
      var values = filterValues.split(',')
      for (var i = 0; i < values.length; i++) {
        if (item[filterField] === values[i]) {
          array.push(item)
          break
        }
      }
    } else {
      array.push(item)
    }
    if (children instanceof Array) {
      array = array.concat(treeProduceList(children, childrenField, filterField, filterValues))
    }
  })
  return array
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值