react源码分析(3):react的事件委托机制

在开始之前,可以先看一下我的另一篇关于dom本身的事件机制《谈谈js点击之后发生了什么》

前言

    如果你有试过输出react事件中的event,你就会发现这个event好像和我们看到的dom事件中的event不太一样,那是因为react在进行dom事件绑定时,不是直接绑定事件的,而是通过所谓的合成事件(SyntheticEvent)进行委托管理的,它是原生事件进行封装后的结果,你可以通过nativeEvent获取原生事件。

通过例子观察

class App extends Component {
  componentDidMount(){
    document.addEventListener('click', function(){
      console.log('document click')
    })
    document.getElementsByClassName('App')[0].addEventListener('click', function(){
      console.log('app click')
    })
    document.getElementsByTagName('button')[0].addEventListener('click', function(e){
      console.log('button click')
      // e.stopPropagation();
    })
  } 
  onClick = (e) => {
    e.stopPropagation() // 能够阻止div.app的触发
    e.nativeEvent.stopImmediatePropagation(); // 能够阻止document的触发
    e.nativeEvent.stopPropagation(); // 什么都阻止不了
    console.log('react button click');
  };
  render() {
    return (
      
{console.log('react app click')}}>
); } }

我们在用react绑定了两个事件,同时在didmount给真实dom也绑定了事件,点击button之后的执行顺序是

button click
app click
react button click
react app click
document click

原理

react并不是我之前所设想的将事件绑定在真实dom上,而是通过自己的事件处理器来处理,将所有的事件都绑定在document上,这样真实点击的时候,冒泡到document上,react再通过document去dispatchEvent统一处理事件

所以上面的stopPropagation就能理解了,e.stopPropagation只能阻止虚拟dom的事件冒泡,但它本身是由document触发的,所以e.nativeEvent.stopPropagation什么也阻止不了,document就是冒泡的顶点,e.nativeEvent.stopImmediatePropagation可以阻止document的事件,这和它本身有关

需要注意的是react在生成真实的dom节点会加入一些东西帮助事件分发
dom节点的react属性(15和16有点区别)

react15.png

react16.png

15和16会有一点细微的区别,具体什么还没有去了解,但是看来对事件的影响不大,15是有一个__rootNodeID来区分组件,16是通过__debugID来区分,如果理解上有错误,欢迎指正哦

  • vdom
// 简化
let vdom = {
  type: 'div',
  props: {
    onClick: function(){
      console.log('react app click')    
    },
    children: [
      {
        type: 'button',
        props: {
           onClick: function() {
              console.log('react button click')    
           }
        }
      }
    ]
  }
}
  • 注册事件
let bankForRegistrationName = {}; // 回调事件的保存
// react构建真实dom树
...
// 注册事件
bankForRegistrationName = {
    // 数字是_debugID,react用于识别每一个dom
    5: {
      click: function(){
        console.log('react app click')    
      },
    },
    6: {
      click: function(){
        console.log('react button click')    
      },
    }
}
  • 事件触发
// 合成事件简单实现
function SyntheticEvent(e) {
  ...
  this.nativeEvent = e;
  ...
}
// e: event, type: 事件类型
function dispatchEvent(e, type) {
  let synE = new SyntheticEvent(e);
  // 执行监听事件
  let debugID = e.target.__reactInternalInstance$om8tco7dvl._debugID;
  bankForRegistrationName[debugID][type](synE); 
}
// document事件委托
document.addEventListener('click', function(e) {
  dispatchEvent(e, 'click');
})

总结

    基本上就是这样了(当然没有对冒泡做处理,react会遍历自己的vdom去执行冒泡)

  1. 事件管理中心(bankForRegistrationName)会在react-render过程中保存所有所有dom事件
  2. document作为事件委托者,用来分发事件(dispatchEvent),通过dom节点唯一标识(_debugID)去事件管理(bankForRegistrationName)触发事件

你可能感兴趣的:(react源码分析(3):react的事件委托机制)