好久没写博客了,前段时间太忙以至于平时的积累都记录在内网的 wiki 里,趁着这几天有空,将这段时间所积累的干货慢慢的分享出来,如果内容有不正确的地方,欢迎纠正。
本博客大概介绍一下 react 的事件机制,并给出整体的设计图,但不涉及 react 底层的源码结构分析。
现象:
demo1: <div onclick=”handle()”>ni</div>
demo2: render() { return <div onClick={this.handle}>ni</div> }
虽然两个例子都是通过标签内嵌的方式将 click 事件进行绑定,但其中的原理是不一样的,demo1 是采用原生的事件处理,demo2 是采用 react 的合成事件机制处理;
合成事件:
对于 jsx 来说,是采用了类似于 DOM0 的事件绑定的方式进行处理,它会收到一个合成事件对象(synthicevent),该对象集成了原生事件对象的所有特性,而且还是事件冒泡机制,并且能支持 stopPropagation 和 preventDefault 两种方法;
原生事件与合成事件的区别:
原生的事件绑定是采用小写 onclick,而 react 则是采用大写 onClick;
原生事件绑定的是一个 js 的字符串,而 react 采用的是一个函数的指针;
合成事件的实现机制:
事件机制原型图:
该图大概的表示了 react 的事件机制的整体结构图,接下来具体说说它里面的原理。
真正的监听者:
对于 react 来说,虽然事件是绑定在 v -dom 中,但其实真正的监听者只有一个,就是结构中最外层的 document 对象进行监听,主要采用了事件冒泡的方式,将 v -dom 中触发的事件包装成一个合成事件,然后通过事件冒泡的方式,最终冒泡到最外层的 document 监听和执行(就是事件委托);
事件注册:
事件注册是在组件生成的时候,将 v -dom 中所有的事件都对应的原生事件都注册在 document 的监听器中,例如 onClick 对应的原生事件是 onclick,如果 v -dom 中有绑定了 onClick,那么就会将对应的 onclick 事件注册在 document 中,整个注册过程可以三个阶段:
1)将 v -dom 中所涉及到的绑定事件所对应的原生事件都在 enqueuePutListener 中绑定到 document 身上;
2)将 v -dom 中所有事件的事件处理函数都存放在 listenerBank 中,存放的方式是以 registrtionname 和 key 作为索引存放,其中 registrtionname 是事件名,key 是 instance 的 id 值,所以形式是:
listenerBank[registrtionname][key] = listener
这样的好处是将可能要触发的事件分门别类,以及将对应的 listener 也分门别类存放;为了就是在事件触发的时候,能从 listenerBank 中取出同类型的 listener 存放在 dispatchListener 中;
3)最后将 dispatchEvent 作为 callback 函数,放在 addEventListener 和 removeEventListener 里面,等待事件的触发;
合成事件:
当事件触发的时候,不会直接将原生的事件发送到最外层的 document 中,而是经过处理,将处理后的事件发送到 document 中;事件合成的经过:获取原生事件,并通过原生事件的类型和所在组件的 id 值,在 listenerBank 中取出对应的 listener 函数,并存放在_dispatchListener 队列中,然后将该实例存放到_dispatchInstance 队列中,这样一来,可以将同类型的事件函数都按照顺序存放在_dispatchListener 中,最后一同处理;
事件发布:
当事件触发时,会先原生事件变成合成事件,然后传递到 document 中,然后 document 会通过 dispatchEvent 回调函数依次执行 dispatchListener 中同类型的事件监听函数。
整个设计图:
以上是对 react 的合成事件一个大概的介绍,里面还有很多细节和原理没说到,有兴趣的同学可以进一步研究一下源码的细节。