好像很无聊的一道题,但真就在业务场景中遇到了,你说难搞不难搞。
要是觉得啰嗦,直接看 结尾 吧
先聊聊背景
有一个 Menu 组件,当你点击后会高亮,并且需要获取里面的文字,比如「全部」、「装机必备」、「图标」
它的 HTML 结构不难想象,一个 DIV 里包着三个元素 —— SVG、文本和数字。写法很多,我简单写个通用的。
<div id="parent">
<div id="svg">
<svg>...</svg>
</div>
<div id="text">
<span>全部</span>
</div>
<div id="number">
<span>1279</span>
</div>
</div>
Menu 组件选中状态的改变肯定需要监听事件,那就给它绑一个 onchange 事件吧
<!-- tsx grammar -->
const handleClick = (e: MouseEvent) => {
console.log(e.target.innerText)
}
<div id='parent' onClick={handleClick}>
...
</div>
我们希望只要点击就会打印「全部」,可是,因为事件冒泡的原因,e.target
可能会绑定到不同的元素上,导致打印结果出现多种可能「undefined」「全部」或「1279」。
为了确保结果的准确,我们需要重新定位元素 —— 只要具有相同代理的子元素触发得到的 target,全部重定向到指定元素。
表达不清楚就看看例子吧。假设我们当前 e.target
的值为 <span>1279</span>
,我们想实现 e.target.innerText === '全部'
。
首先找到绑定事件代理的元素 P,这里是 <div id='parent'></div>
;
然后判断这个 P 元素是不是 e.target
的父元素,因为事件代理只负责自己的子元素;
如果 e.target
是 P 的子元素,那就定位到目标节点,拿它的值
const p = document.getElementById('parent')
let flag = false
while (e.target) {
if (e.target === p) flag = true
e.target = e.target.parentNode
}
if (flag) {
const target = document.getElementById('text')
const res = target.innerText
}
解决思路
有两种方案: 1. 判断节点 Children 是否为 Parent 的子节点 2. 判断节点 Parent 是否为 Children 的父节点显然「第二种更好」,因为子节点可能有多个,但父节点是唯一的。
遍历子节点四舍五入约等于做一次深度或广度优先遍历,能不费时费事么;
遍历父节点只要判断父节点是否为空,一直循环往上找就好了,谁是谁的娃一眼就看出来。
中心思想,还是上面的代码为例
只要判断子节点的父节点是否和 Parent 节点相同,如果不相同,
那就判断子节点的父节点的父节点是否和 Parent 节点相同,如果不相同,
那就判断子节点的父节点的父节点的父节点是否和 Parent 节点相同,如果不相同,
那就…
不套娃了哈哈哈,show u the code
// read e prop from onchange(e: Event)
while (e.target) {
if (e.target === parent) return true
e.target = e.target.parentNode
}
我记得直接给 e.target
赋值会有警告,懒得测了,记住思想就好啦!
2022.4.6更新
xdm,看react文档的时候突然发现node对象有一个方法叫做 includes,专门判断包含关系,直接搜 includes mdn 就完了,冲啊!(临时用 pad 更新的文章,辛苦hxd自己搜一下哈)