react事件机制底层原理

1,355 阅读3分钟

阅读须知

1.采用react版本为v16.10.2 细节可能有所出入,但是原理都是一样的。
2.请通过无痕模式调试否则插件容易干扰我们的调试。

一、一个demo引发的问题

const App =()=> { return <input onChange={()=>{console.log('这是一个onChang额事件')}}/> }

这是一个平淡无奇的最简单demo,我们看到在input元素绑定onChange事件,当我们定位到这个dom元素的时候发现react为我们添加了input、keydown、blur、change等等事件,而且这些事件都是绑定到document上,并没有绑定到我们的input元素中。那么react究竟做了什么处理呢。

image.png

二、react事件是怎么被绑定的

首先我们要明白,react中的事件不是原生事件是合成事件,如何合成的呢,我们debugger源码就知道了。当我们打断点调试的时候首先会出来这些东西,首先我们看到这个函数,在react-dom的第一次运行的时候会出现这个。 image.png 其主要作用初始化一些事件的插件 我们拿ChangeEventPlugin来简单的说一下里面主要是用来干什么的吧。


var ChangeEventPlugin = {
  eventTypes: {
   change: {
    phasedRegistrationNames: {
      bubbled: 'onChange',
      captured: 'onChangeCapture'
    },
    dependencies: [TOP_BLUR, TOP_CHANGE, TOP_CLICK, TOP_FOCUS, TOP_INPUT, TOP_KEY_DOWN, TOP_KEY_UP, TOP_SELECTION_CHANGE]
  }},
  _isInputEventSupported: isInputEventSupported,
  extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags) {
    var targetNode = targetInst ? getNodeFromInstance$1(targetInst) : window;
    var getTargetInstFunc, handleEventFunc;

    if (shouldUseChangeEvent(targetNode)) {
      getTargetInstFunc = getTargetInstForChangeEvent;
    } else if (isTextInputElement(targetNode)) {
      if (isInputEventSupported) {
        getTargetInstFunc = getTargetInstForInputOrChangeEvent;
      } else {
        getTargetInstFunc = getTargetInstForInputEventPolyfill;
        handleEventFunc = handleEventsForInputEventPolyfill;
      }
    } else if (shouldUseClickEvent(targetNode)) {
      getTargetInstFunc = getTargetInstForClickEvent;
    }

    if (getTargetInstFunc) {
      var inst = getTargetInstFunc(topLevelType, targetInst);

      if (inst) {
        var event = createAndAccumulateChangeEvent(inst, nativeEvent, nativeEventTarget);
        return event;
      }
    }

    if (handleEventFunc) {
      handleEventFunc(topLevelType, targetNode, targetInst);
    } // When blurring, set the value attribute for number inputs


    if (topLevelType === TOP_BLUR) {
      handleControlledInputBlur(targetNode);
    }
  }
}

其中dependencies属性主要是告诉我们change事件是依赖于它下面的一些事件来合成的。而phasedRegistrationNames是在冒泡或者捕获两个阶段做的一些处理,react会在不同阶段调用不同的处理方法。 所以初始化完成之后他们的结构是这样的会形成registrationNameModules与registrationNameDependencies这两个对象如下图所示 image.png

形成这两个对象是在diff过程更好的对事件进行处理。

image.png registrationNameModules主要是在diff过程中如果检测到fiber上存在像onClick这些方法就会调用这些插件进行处理。 image.png image.png

判断合成事件依赖于那些事件然后调用registrationNameDependencies对应的事件所依赖的原生事件进行绑定。绑定到了document上。

到现在为止我们的react事件绑定完成了并且绑定到了document对象上。

三、react事件如何触发的

因为我们在document上绑定了事件,当我们点击的时候肯定知道是哪一个元素触发了这个事件,但是还有一个问题就是我们怎么找到我们在代码写的事件处理函数,因为我们的事件处理函数最后被react初始化到了对应的fiber对象中了所以如何通过dom元素找到其对应的fiber对象,这个就是我们需要探讨的。 image.png

这个就是react如何找到对应的fiber对象,是这样的,其中原生dom与其对应的fiber对象中存在一个指向问题,原生dom通过里面一个唯一key指向dom对应的fiber,而fiber中stateNode对应的也是该原生dom对象。所以我们就可以找到了我们写在react中的处理函数。即原生dom->fiber->memoizedProps image.png 这里面其实就是冒泡时候先寻找父组件如果父组件有这个事件就加到event._dispatchListeners上,然后遍历这个数组拿到回调然后触发事件 image.png 为啥用e.preventDefault() 和 return false 阻止不了事件,就是这个原因 。

好了,我们的react事件绑定机制讲完了,如果有什么意见或者建议请评论告诉我。。