Open buxuku opened 3 years ago
在这之前,我们已经能够在DOM上绑定一些原生的属性了,比如id,style这些.但还没有实现交互行为,比如点击事件之类的,给DOM绑定事件,我们一般采用给DOM添加onxxx这样的事件,或者通过事件委托addEventListener这样的方式.
id
style
onxxx
addEventListener
不过在React中,它自己实现了一套事件机制,就是所谓的合成事件,它解决了不同浏览器对于事件处理的各种兼容性问题,同时把所有的事件都绑定到了Document上面.因此其它DOM上面没有了事件绑定,从而也减少了内存的开销.
Document
React以队列的方式,会从触发的元素向父元素依次进行回溯,来调用所有需要执行的事件函数.
那在实现它的时候,我们大概的思路就是,在事件注册的时候,把事件都绑定到Document上面去,然后在事件触发的时候,通过冒泡的形式来触发对应的事件即可.
在react-dom/index.js文件里面的方法中,我们增加对事件类型的处理
react-dom/index.js
function renderAttributes(dom, attributes = {}){ for(let key in attributes){ const value = attributes[key]; if(!value || key === 'children') continue; // 属性无值不处理,children也单独处理 if(key === 'style'){ for(let attr in value){ dom.style[attr] = value[attr]; } + } else if(key.startsWith('on')){ + addEvent(dom, key.toLocaleLowerCase(), value) } else { dom[key] = value; } } return dom; }
然后新建一个react-dom/event.js文件,先实现我们的事件注册方法,因为同一个DOM可能会绑定多个不同类型的事件,我们给DOM上增加一个store属性来缓存绑定的事件.相当于维护一个事件池.并对该事件类型在Document上面进行绑定.当用户进行对应的操作的时候,我们就能从event对象里面的target来获取到我们的目标元素.这也就是好比我们对一个ul包裹的li列表,我们要对li进行事件绑定的话, 无须对每一个li都去绑定事件,只需要在ul上面进行事件绑定即可,因为我们可以通过target来获取到到用户到底是点击的哪一个li的.
react-dom/event.js
event
target
ul
li
/** * 事件注册,所有的事件都绑定到Document上面 * @param dom * @param key * @param handler */ export function addEvent(dom, eventType, handler){ dom.store = dom.store || {}; dom.store[eventType] = handler; // 给dom增加一个store属性来缓存当前dom上面绑定的所有事件. if(!document[eventType]){ // 同一类型的事件在document上面只需要绑定一次 document[eventType] = dispatchEvent; } }
在目标元素触发事件的时候,我们需要把事件不断冒泡上去,我们需要继续来实现上一步未写的dispatchEvent方法
dispatchEvent
/** * 事件派发 * @param event */ function dispatchEvent(event){ let { target, type } = event; let eventType = `on${type}`; let syntheticEvent = createSyntheticEvent(event); // 模拟事件冒泡,一直向父级元素进行冒泡 while(target){ let { store } = target; let handler = store && store[eventType]; handler && handler.call(target, syntheticEvent); target=target.parentNode; } } /** * 处理浏览器的一些兼容性处理 * @param event * @returns {{}} */ function createSyntheticEvent(event){ let syntheticEvent = {}; for(let key in event){ syntheticEvent[key] = event[key]; } return syntheticEvent; }
这里对syntheticEvent没有去完整实现,它里面处理了浏览器的各自兼容性.同时因为React是自己维护的一套事件队列,所以可以看到上面我们在实现冒泡的时候,是通过模拟的方式来实现的.这样目标元素就可以一层层触发上去.
syntheticEvent
在react-dom/index.js中导入这个addEvent方法,同时修改src/index.js文件如下
addEvent
src/index.js
import React from './react'; import ReactDOM from './react-dom'; class Hello extends React.Component { handleClick = (type) => { console.log('clicked', type); } render() { return <h1 id="title" className='title' onClick={() => this.handleClick('h1')}>hello <span style={{color: 'red'}} onClick={() => this.handleClick('span')}>{this.props.name}</span></h1> } } ReactDOM.render( <Hello name='world'/>, document.getElementById('root') );
运行发现,点击h1和span标签里面的内容都能正常触发事件了,点击span也能正常触发事件的冒泡了.
h1
span
在这之前,我们已经能够在DOM上绑定一些原生的属性了,比如
id
,style
这些.但还没有实现交互行为,比如点击事件之类的,给DOM绑定事件,我们一般采用给DOM添加onxxx
这样的事件,或者通过事件委托addEventListener
这样的方式.不过在React中,它自己实现了一套事件机制,就是所谓的合成事件,它解决了不同浏览器对于事件处理的各种兼容性问题,同时把所有的事件都绑定到了
Document
上面.因此其它DOM上面没有了事件绑定,从而也减少了内存的开销.React以队列的方式,会从触发的元素向父元素依次进行回溯,来调用所有需要执行的事件函数.
那在实现它的时候,我们大概的思路就是,在事件注册的时候,把事件都绑定到
Document
上面去,然后在事件触发的时候,通过冒泡的形式来触发对应的事件即可.事件注册
在
react-dom/index.js
文件里面的方法中,我们增加对事件类型的处理然后新建一个
react-dom/event.js
文件,先实现我们的事件注册方法,因为同一个DOM可能会绑定多个不同类型的事件,我们给DOM上增加一个store属性来缓存绑定的事件.相当于维护一个事件池.并对该事件类型在Document
上面进行绑定.当用户进行对应的操作的时候,我们就能从event
对象里面的target
来获取到我们的目标元素.这也就是好比我们对一个ul
包裹的li
列表,我们要对li
进行事件绑定的话, 无须对每一个li
都去绑定事件,只需要在ul
上面进行事件绑定即可,因为我们可以通过target
来获取到到用户到底是点击的哪一个li
的.事件派发
在目标元素触发事件的时候,我们需要把事件不断冒泡上去,我们需要继续来实现上一步未写的
dispatchEvent
方法这里对
syntheticEvent
没有去完整实现,它里面处理了浏览器的各自兼容性.同时因为React是自己维护的一套事件队列,所以可以看到上面我们在实现冒泡的时候,是通过模拟的方式来实现的.这样目标元素就可以一层层触发上去.在
react-dom/index.js
中导入这个addEvent
方法,同时修改src/index.js
文件如下运行发现,点击
h1
和span
标签里面的内容都能正常触发事件了,点击span
也能正常触发事件的冒泡了.