前言
最近遇到個模糊匹配的小需求,需要對樹形結(jié)構(gòu)的內(nèi)容進(jìn)行模糊匹配,對樹中的節(jié)點(diǎn),只要是出現(xiàn)了相應(yīng)文本,就把節(jié)點(diǎn)以上的整個樹都展示出來
之前我寫過一些針對id的匹配,和這個還不太一樣,因?yàn)?code>id的匹配是唯一的,只要進(jìn)行一次遞歸(遍歷),找到就能返回相應(yīng)節(jié)點(diǎn),也做過將樹形數(shù)據(jù)可視化的功能,但是要根據(jù)規(guī)則記錄樹形結(jié)構(gòu),就稍微麻煩點(diǎn)。
分類樹

基本上就是這樣一個分類樹,數(shù)據(jù)結(jié)構(gòu)如下:
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'
}]
}]
要求
當(dāng)我輸入“測試”時,把所有包含“測試”節(jié)點(diǎn)都顯示出來,也就是除了最后的“這里就不是”節(jié)點(diǎn),其他的都要出來
當(dāng)我輸入“這里都不是”時,雖然他的父級節(jié)點(diǎn)都沒有包含這個字符,但是作為他的父節(jié)點(diǎn),所以也都要顯示出來(作為樹形結(jié)構(gòu))
當(dāng)我輸入“測試2”時,就只會顯示根數(shù)組的第二個元素,因?yàn)橹挥羞@個分支中包含“測試2”的完整文本
總結(jié)一下,就是根據(jù)輸入的文本,要顯示出最深的包含此文本的節(jié)點(diǎn)的整個分支。
設(shè)計(jì)和代碼
首先,根據(jù)需求,要對樹進(jìn)行遍歷,給每一個節(jié)點(diǎn)一個字段,用來判斷是否可以輸出
// 給每個分支上有所匹配的設(shè)置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
})
}
這個判斷的函數(shù)isNeedBranch也是一個遞歸,需要遍歷整個樹的每一個節(jié)點(diǎn),只有當(dāng)子節(jié)點(diǎn)中沒有匹配到,并且自身也沒有匹配到時,才會返回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
}
最后,對遍歷過的樹進(jìn)行篩選,通過filter進(jìn)行遞歸,把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
})
}
拿到數(shù)據(jù)后,還要進(jìn)行渲染,渲染出樹形DOM,當(dāng)然,有jsx或者模板的話,就不用這么麻煩
但是,要把拿到的數(shù)據(jù)進(jìn)行一次deepcopy,或者每次點(diǎn)擊都重新拿取,因?yàn)?code>forEach或map方法都改變原數(shù)組
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
}
