最近接触 webdom 比较多,于此记录下部分 JavaScript 观察(或者说监听) Webdom 的姿势
我们最近在开发一个在线原型编辑,用户可以在我们的编辑器上编辑一些产品原型类似的东西,相似的产品有 墨刀 ,但是我们的方向主要是图文。
里面涉及到很多 Dom 的监听与变化,事件主要在上一篇文章说过,这次说说 Dom 的变化方面。
比较主要的就是一些 Dom 标签的添加与移除,有一块比较特殊的:
某个 element , 当这个元素被移除的时候,释放另外一个 element 的状态
看起来好像不是很麻烦,只要在移除的地方释放就完事了。类似于这样的:
1 2 3 4 5 6 7 8 9
| const trigger = document.querySelector('.someTrigger'), remove = document.querySelector('.someRemove'), release = document.querySelector('.someRelease');
trigger.addEventListener('click', function() { remove.remove(); release.setAttribute('some_state', 'release'); ...other code }, false);
|
后面我发现,这个状态简直有毒,因为业务的相关性太大,居然有十几个场景要设置的,而且有时候添加 Dom 节点也会涉及到。当然了,这不能难到我,我把代码修改了。变成了这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const trigger1 = document.querySelector('.someTrigger'), remove = document.querySelector('.someRemove'), release = document.querySelector('.someRelease')
trigger1.addEventListener('click', beTrigger, false); trigger2.addEventListener('click', beTrigger, false); trigger3.addEventListener('dblclick', beTrigger, false); trigger4.addEventListener('scroll', beTrigger, false); trigger5.addEventListener('focus', otherTrigger, false);
function beTrigger() { remove.remove(); release.setAttribute('some_state', 'release'); }
function otherTrigger() { const child = document.createElement('div'); trigger.appendChild(child); release.setAttribute('some_state', 'other'); }
|
这种处理方式很好,我提交代码进行了测试,没问题。于此我就下班了。
下班的路上,我还是对提交的代码很不安,虽然代码看起来什么问题,也能够处理业务,但是每一次有一个场景要触发,我就得添加一条 someElement.addEventListener('someEvent', otherFunction, true|false),重复写这个对我这样有代码强迫的人很难受。
回家后我发现基本都是 Dom 节点在新增或者移除而触发的。
可不可以监听容器下的 Dom 节点的变化呢?
答案是可以的。
我认为这个应该也是类似于事件处理的一类,于是我发现了 DOMNodeRemoved 和 DOMNodeInserted,它们被归类在了 Mutation Events里面,但是已经被 Web 标准废弃了,替代它的是 MutationObserver。
看了它的示例,我用 React 改写成了如下代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| import React from 'react'; import { Button, Right } from 'anypath';
let observer = null, content;
class ObserveDom extends React.Component { constructor(props) { super(props); this.addElement = this.addElement.bind(this); }
componentDidMount () { observer = new MutationObserver(this.observerCallBack); this.initContent(); observer.observe(content, { childList: true }); }
componentWillUnmount() { observer.disconnect(); observer = null; }
observerCallBack (mutationsList, observer) { Object.keys(mutationsList).map(function (index) { const item = mutationsList[index]; switch (item.type) { case 'childList': console.info(item); console.info('childList has been modified'); break; default: break; } }) }
initContent() { if (content instanceof Element == false) content = document.getElementById('content'); }
addElement() { const child = document.createElement('div'); Object.keys(itemStyle).map(function (item) { child.style[item] = itemStyle[item]; }); child.innerHTML = 'Item'; content.appendChild(child); }
render() { return <div id='content'> <header> <title>ObserveDom</title> <Right> <Button onClick={this.addElement}>Add Element</Button> <Button onClick={this.addElement}>Remove Element</Button> </Right> </header> </div> } }
const itemStyle = {
export default ObserveDom;
|
你甚至可以用这个 MutationObserver 给观察对象一个钩子,类似生命周期那样的(不过不是很有必要),这里我使用了 class 没有用 hooks ,因为用前者比较对生命周期划分的比较清晰,容易理解(当然用 hooks 也可以实现)。