React合成事件原理

142 阅读3分钟

合成事件原理例子:

class Demo extents React.Component {
	 render() {
    return (
      <div
        className="outer"
        onClick={() => {
          console.log('outer 冒泡 【合成】');
        }}
        onClickCapture={() => {
          console.log('outer 捕获 【合成】');
        }}
      >
        <div
          className="inner"
          onClick={() => {
            console.log('inner 冒泡 【合成】');
          }}
          onClickCapture={() => {
            console.log('inner 捕获 【合成】');
          }}
        ></div>
      </div>
    );
  }
  componentDidMount() {
    document.addEventListener(
      'click',
      () => {
        console.log('document 捕获');
      },
      true
    );
    document.addEventListener(
      'click',
      () => {
        console.log('document 冒泡');
      },
      false
    );

    document.body.addEventListener(
      'click',
      () => {
        console.log('body 捕获');
      },
      true
    );
    document.body.addEventListener(
      'click',
      () => {
        console.log('body 冒泡');
      },
      false
    );
    let root = document.querySelector('#root');

    root.addEventListener(
      'click',
      () => {
        console.log('root 捕获');
      },
      true
    );
    root.addEventListener(
      'click',
      () => {
        console.log('root 冒泡');
      },
      false
    );
  let outer = document.querySelector('.outer');
    outer.addEventListener(
      'click',
      () => {
        console.log('outer 捕获【原生】');
      },
      true
    );
    outer.addEventListener(
      'click',
      () => {
        console.log('outer 冒泡【原生');
      },
      false
    );
    let inner = document.querySelector('.inner');
    inner.addEventListener(
      'click',
      () => {
        console.log('inner 捕获【原生】');
      },
      true
    );
    inner.addEventListener(
      'click',
      () => {
        console.log('inner 冒泡【原生');
      },
      false
    );
  }
}

image.png

图文分析

image.png

<script>
  	const root= document.querySelector('#root')
  	const outer= document.querySelector('#outer')
		const inner= document.querySelector('#inner')
		//经过视图渲染解析,outer/inner上都有onXxx/onXxxCapure这样的属性
		outer.onClick=()=>{console.log('outer 冒泡【合成】')}
  	outer.onClickCapture=()=>{console.log('outer 捕获【合成】')}
		inner.onClick=()=>{console.log('inner 冒泡【合成】')}
  	inner.onClickCapture=()=>{console.log('inner 捕获【合成】')}
  	//给#root做事件绑定
  	root.addEventListerner('click,(ev)=>{
      	let path = ev.path; //path:[事件源->...->window] 所有祖先元素
      	console.log(path)
        [...path].reverse().forEach(ele=>{
          	let handle = ele.onClickCapture;
          	if(handle) handle()
        })
    },true)
  	root.addEventListerner('click,(ev)=>{
      	let path = ev.path;
      	console.log(path)
         path.forEach(ele=>{
          	let handle = ele.onClick;
          	if(handle) handle()
        })
    },false)
</script>

图文分析

image.png

总结:合成事件原理

React中合成事件的处理原理: ‘绝对不是’给当前元素基于addEventListener单独做的事件绑定,React中的合成事件,都是基于‘事件委托’(事件传播机制)处理的

  • 在React17及以后版本,都是委托给#root这个容器【捕获和冒泡都做了委托】
  • 在17版本以前,都是为委托给document容器的【而且只做了冒泡阶段的委托】,在委托的方法中,把onXxx/onXxxCapture合成事件属性进行执行
  • 对于没有实现事件传播机制的时间,才是单独做的事件绑定【例如:onMouseEnter/onMouseLeave...】

在组件渲染的时候,如果发现JSX元素属性中有onXxx/onXxxCapture这样的属性,不会给当前元素直接做事件绑定,只是把绑定的方法赋值给元素的相关属性!!

outer.onClick=()=>{console.log('outer 冒泡【合成】')} //这不是DOM 0级事件绑定【这样的才是 outer.onClick】
outer.onClickCapture=()=>{console.log('outer 捕获【合成】')}

然后对#root这个容器做了事件绑定【捕获和冒泡都做了】

原因: 因为组件中所渲染的内容,最后都插入到#root容器中,这样点击页面中任何一个元素,最后都会把#root的点击行为触发,而是在#root绑定的方法中,把之前给元素设置的onXxx/onXxxCapture属性,在相应的阶段执行

关于阻止事件

  • ev.stopPropagation() 合成事件对象中的‘阻止事件传播’:阻止原生的事件传播&阻止合成事件中的事件传播
  • ev.nativeEvent.stopPropagation() 原生事件对象中的‘阻止事件传播’:只能阻止原生事件的传播
  • ev.nativeEvent.stopImmediatePropagation() 原生事件对象的阻止事件传播,只不过可以阻止#root上其他绑定的方法执行

在React16中,关于合成事件对象的处理,React内部是基于‘事件对象池’,做了一个缓存机制!React17及以后,是去掉了这套事件对象池和缓存机制的

  • 当每一次事件触发的时候,如果传播到了委托的元素上【document/#root】,在委托的方法中,我们首先会对内置事件对象做统一处理,生产合成事件对象

=========================================

在React16版本中

  • 为了防止每一次都是重新创建出新的合成事件对象,它设置了一个事件对象池【缓存池】
    • (本次事件触发,获取到事件操作的相关信息,我们从事件对象池中获取存储的合成事件对象,把信息赋值给相关成员)
    • 等待本次操作结束,把合成事件对象中的成员信息都清空掉(ev.persist()可以把合成事件对象中的信息保留下来),再放入到事件对象池

图:

image.png