前言
最近遇到个模糊匹配的小需求,需要对树形结构的内容进行模糊匹配,对树中的节点,只要是出现了相应文本,就把节点以上的整个树都展示出来
之前我写过一些针对id
的匹配,和这个还不太一样,因为id
的匹配是唯一的,只要进行一次递归(遍历),找到就能返回相应节点,也做过将树形数据可视化的功能,但是要根据规则记录树形结构,就稍微麻烦点。
分类树
基本上就是这样一个分类树,数据结构如下:
const data = [{
id: 1,
txt: '测试1',
children: [{
id: 11,
txt: '测试11',
children: [{
id: 111,
txt: '测试111',
children: [{
id: 1111,
txt: '这里就不是',
children: []
}]
}]
}, {
id: 12,
txt: '测试12',
children: [{
id: 121,
txt: '测试121',
children: [{
id: 1211,
txt: '测试1211',
children: []
}]
}]
}]
}, {
id: 2,
txt: '测试2',
children: [{
id: 21,
txt: '测试21'
}]
}]
要求
当我输入“测试”时,把所有包含“测试”节点都显示出来,也就是除了最后的“这里就不是”节点,其他的都要出来
当我输入“这里都不是”时,虽然他的父级节点都没有包含这个字符,但是作为他的父节点,所以也都要显示出来(作为树形结构)
当我输入“测试2”时,就只会显示根数组的第二个元素,因为只有这个分支中包含“测试2”的完整文本
总结一下,就是根据输入的文本,要显示出最深的包含此文本的节点的整个分支。
设计和代码
首先,根据需求,要对树进行遍历,给每一个节点一个字段,用来判断是否可以输出
// 给每个分支上有所匹配的设置isNeed
function setFlag (data, keyword) {
return data.map(item => {
item.isNeed = isNeedBranch(item, keyword)
if(item.children && item.children.length) {
setFlag(item.children, keyword)
}
return item
})
}
这个判断的函数isNeedBranch
也是一个递归,需要遍历整个树的每一个节点,只有当子节点中没有匹配到,并且自身也没有匹配到时,才会返回false
// 判断这条分支上有没有匹配到的
function isNeedBranch (item, keyword) {
let flag1 = false,
flag2 = false
if(item.txt.indexOf(keyword) > -1) {
flag1 = true
} else if(item.children && item.children.length) {
item.children.forEach(child => {
if(isNeedBranch(child, keyword)) {
flag2 = true
}
})
}
return flag1 || flag2
}
最后,对遍历过的树进行筛选,通过filter
进行递归,把filter
返回的值赋给chilren
,完成筛选。
// 过滤掉分支上isNeed为false的元素
function treeFilter (data) {
return data.filter((item, index) => {
if(item && item.children && item.children.length) {
item.children = treeFilter(item.children)
}
return item.isNeed
})
}
拿到数据后,还要进行渲染,渲染出树形DOM,当然,有jsx
或者模板的话,就不用这么麻烦
但是,要把拿到的数据进行一次deepcopy
,或者每次点击都重新拿取,因为forEach
或map
方法都改变原数组
function getShowHTML(val, data) {
if(!val) return ''
let dataCopy = JSON.parse(JSON.stringify(data)) // 一个简单的deepCopy
let filterTree = treeFilter(setFlag(dataCopy, val))
return (filterTree && filterTree.length) ? getTreeHtml(filterTree) : ''
}
function getTreeHtml(tree) {
let rootHTML = document.createElement('ul')
rootHTML.style.id = 'root'
let search = (nodeHTML, nodeJSON) => {
nodeJSON.forEach(item => {
// console.log('nodeHTML', nodeHTML)
if(item.children && item.children.length) {
let thisNode = document.createElement('li')
let thisNodeUl = document.createElement('ul')
thisNode.append(item.txt)
thisNode.append(thisNodeUl)
// console.log('thisNode.children', thisNode)
search(thisNode.children[0], item.children)
nodeHTML && nodeHTML.append(thisNode)
} else {
let li = document.createElement('li')
li.append(item.txt)
li.style.id = item.id
nodeHTML && nodeHTML.append(li)
}
})
}
search(rootHTML, tree)
return rootHTML
}