连系源码完全明白 react事宜机制道理 04 - 事宜实行

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

媒介

这是 react 事宜机制的第四节-事宜实行,一同研讨下在这个历程当中重要经由了哪些关键步骤,本文也是react 事宜机制的完结篇,愿望本文可以让你对 react 事宜实行的道理有肯定的邃晓。

文章涉及到的源码是基于 react15.6.1版本,虽然不是最新版本然则也不会影响我们对 react 事宜机制的团体把握和邃晓。

回忆

先简朴的回忆下上一文,事宜注册的效果是是把一切的事宜回调保留到了一个对象中

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

那末在事宜触发的历程当中上面这个对象有什么用途呢?

实在就是用来查找事宜回调。

内容纲要

依据我的邃晓,事宜触发历程总结为重要下面几个步骤

1.进入一致的事宜分发函数(dispatchEvent)

2.连系原生事宜找到当前节点对应的ReactDOMComponent对象

3.举行事宜的合成

3.1依据当前事宜范例天生指定的合成对象

3.2封装原生事宜和冒泡机制

3.3查找当前节点以及他的一切父级

3.4在listenerBank查找事宜回调并合成到 event(合成事宜终了)

4.批量处置惩罚合成事宜内的回调事宜(事宜触发完成 end)

说再多不如配个图

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

举个栗子

在说详细的流程前,先看一个栗子,背面的剖析也是基于这个栗子

handleFatherClick=(e)=>{
        console.log('father click');
    }

    handleChildClick=(e)=>{
        console.log('child click');
    }

    render(){
        return <div className="box">
                    <div className="father" onClick={this.handleFatherClick}> father
                        <div className="child" onClick={this.handleChildClick}>child </div>
                    </div>
               </div>
    }
    

看到这个熟习的代码,我们就已知道了实行效果。

当我点击 child div 的时刻,会同时触发father的事宜。

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

1、进入一致的事宜分发函数 (dispatchEvent)

当我点击child div 的时刻,这个时刻浏览器会捕捉到这个事宜,然后经由冒泡,事宜被冒泡到 document 上,交给一致事宜处置惩罚函数 dispatchEvent 举行处置惩罚。(上一文中我们已说过 document 上已注册了一个一致的事宜处置惩罚函数 dispatchEvent)

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

2、连系原生事宜找到当前节点对应的ReactDOMComponent对象

在原生事宜对象内已保留了对应的ReactDOMComponent实例,应该是在挂载阶段就已保留了

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

看下ReactDOMComponent实例的内容

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

3、最先举行事宜合成

事宜的合成,冒泡的处置惩罚以及事宜回调的查找都是在合成阶段完成的。

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

3.1 依据当前事宜范例找到对应的合成类,然后举行合成对象的天生

//举行事宜合成,依据事宜范例取得指定的合成类
var SimpleEventPlugin = {
    eventTypes: eventTypes,
    extractEvents: function extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
        var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
        //代码已省略....
        var EventConstructor;

        switch (topLevelType) {
            //代码已省略....
            case 'topClick'://【这里有一个不解的处所】 topLevelType = topClick,实行到这里了,然则这里没有做任何操纵
                if (nativeEvent.button === 2) {
                    return null;
                }
            //代码已省略....
            case 'topContextMenu'://而是会实行到这里,获取到鼠标合成类
                EventConstructor = SyntheticMouseEvent;
                break;


            case 'topAnimationEnd':
            case 'topAnimationIteration':
            case 'topAnimationStart':
                EventConstructor = SyntheticAnimationEvent;//动画类合成事宜
                break;

            case 'topWheel':
                EventConstructor = SyntheticWheelEvent;//鼠标滚轮类合成事宜
                break;

            case 'topCopy':
            case 'topCut':
            case 'topPaste':
                EventConstructor = SyntheticClipboardEvent;
                break;
        }

        var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
        EventPropagators.accumulateTwoPhaseDispatches(event);
        return event;//最终会返回合成的事宜对象
    }

3.2 封装原生事宜和冒泡机制

在这一步会把原生事宜对象挂到合成对象的本身,同时增添事宜的默许行动处置惩罚和冒泡机制

/**
 * 
 * @param {obj} dispatchConfig 一个设置对象 包含当前的事宜依靠 ["topClick"],冒泡和捕捉事宜对应的称号 bubbled: "onClick",captured: "onClickCapture"
 * @param {obj} targetInst 组件实例ReactDomComponent
 * @param {obj} nativeEvent 原生事宜对象
 * @param {obj} nativeEventTarget  事宜源 e.target = div.child
 */
function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {

    this.dispatchConfig = dispatchConfig;
    this._targetInst = targetInst;
    this.nativeEvent = nativeEvent;//将原生对象保留到 this.nativeEvent
    //此处代码略.....
    var defaultPrevented = nativeEvent.defaultPrevented != null ? nativeEvent.defaultPrevented : nativeEvent.returnValue === false;

    //处置惩罚事宜的默许行动
    if (defaultPrevented) {
        this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
    } else {
        this.isDefaultPrevented = emptyFunction.thatReturnsFalse;
    }


    //处置惩罚事宜冒泡 ,thatReturnsFalse 默许返回 false,就是不阻挠冒泡
    this.isPropagationStopped = emptyFunction.thatReturnsFalse;
    return this;
}

下面是增添的默许行动和冒泡机制的处置惩罚要领,实在就是转变了当前合成对象的属性值, 挪用了要领后属性值为 true,就会阻挠默许行动或许冒泡。

来看下代码

//在合成类原型上增添preventDefault和stopPropagation要领
_assign(SyntheticEvent.prototype, {
    preventDefault: function preventDefault() {
        // ....略

        this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
    },
    stopPropagation: function stopPropagation() {
        //....略

        this.isPropagationStopped = emptyFunction.thatReturnsTrue;
    }
);

看下 emptyFunction 代码就邃晓了
《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

3.3 依据当前节点实例查找他的一切父级实例存入path

/**
 * 
 * @param {obj} inst 当前节点实例
 * @param {function} fn 处置惩罚要领
 * @param {obj} arg 合成事宜对象
 */
function traverseTwoPhase(inst, fn, arg) {
    var path = [];//寄存一切实例 ReactDOMComponent

    while (inst) {
        path.push(inst);
        inst = inst._hostParent;//层级关联
    }

    var i;

    for (i = path.length; i-- > 0;) {
        fn(path[i], 'captured', arg);//处置惩罚捕捉 ,反向处置惩罚数组
    }

    for (i = 0; i < path.length; i++) {
        fn(path[i], 'bubbled', arg);//处置惩罚冒泡,从0最先处置惩罚,我们直接看冒泡
    }
}

看下 path 长啥样

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

3.4 在listenerBank查找事宜回调并合成到 event(事宜合成终了)

紧接着上面代码

 fn(path[i], 'bubbled', arg);

上面的代码会挪用下面这个要领,在listenerBank中查找到事宜回调,并存入合成事宜对象。


/**EventPropagators.js
 * 查找事宜回调后,把实例和回调保留到合成对象内
 * @param {obj} inst 组件实例
 * @param {string} phase 事宜范例
 * @param {obj} event 合成事宜对象
 */
function accumulateDirectionalDispatches(inst, phase, event) {
    var listener = listenerAtPhase(inst, event, phase);
    if (listener) {//假如找到了事宜回调,则保留起来 (保留在了合成事宜对象内)
        event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);//把事宜回调举行兼并返回一个新数组
        event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);//把组件实例举行兼并返回一个新数组
    }
}

/**
 * EventPropagators.js
 * 中心挪用要领 拿到实例的回调要领
 * @param {obj} inst  实例
 * @param {obj} event 合成事宜对象
 * @param {string} propagationPhase 称号,捕捉capture照样冒泡bubbled
 */
function listenerAtPhase(inst, event, propagationPhase) {
    var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
    return getListener(inst, registrationName);
}

/**EventPluginHub.js
 * 拿到实例的回调要领
 * @param {obj} inst 组件实例
 * @param {string} registrationName Name of listener (e.g. `onClick`).
 * @return {?function} 返回回调要领
 */
getListener: function getListener(inst, registrationName) {
    var bankForRegistrationName = listenerBank[registrationName];

    if (shouldPreventMouseEvent(registrationName, inst._currentElement.type, inst._currentElement.props)) {
        return null;
    }

    var key = getDictionaryKey(inst);
    return bankForRegistrationName && bankForRegistrationName[key];
}

这里要高亮一下
《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

为何可以查找到的呢?
由于 inst (组件实例)里有_rootNodeID,所以也就有了对应关联

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》
到这里事宜合成对象天生完成,一切的事宜回调已保留到了合成对象中。

4、 批量处置惩罚合成事宜对象内的回调要领(事宜触发完成 end)

第3步天生完 合成事宜对象后,挪用栈回到了我们早先实行的要领内

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》


//在这里实行事宜的回调
runEventQueueInBatch(events);

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

到下面这一步中心省略了一些代码,只贴出重要的代码,

下面要领会轮回处置惩罚 合成事宜内的回调要领,同时推断是不是制止事宜冒泡。

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

贴上末了的实行回调要领的代码

/**
 * 
 * @param {obj} event 合成事宜对象
 * @param {boolean} simulated false
 * @param {fn} listener 事宜回调
 * @param {obj} inst 组件实例
 */
function executeDispatch(event, simulated, listener, inst) {
    var type = event.type || 'unknown-event';
    event.currentTarget = EventPluginUtils.getNodeFromInstance(inst);

    if (simulated) {//调试环境的值为 false,按说临盆环境是 true 
        //要领的内容请往下看
        ReactErrorUtils.invokeGuardedCallbackWithCatch(type, listener, event);
    } else {
        //要领的内容请往下看
        ReactErrorUtils.invokeGuardedCallback(type, listener, event);
    }

    event.currentTarget = null;
}

/** ReactErrorUtils.js
 * @param {String} name of the guard to use for logging or debugging
 * @param {Function} func The function to invoke
 * @param {*} a First argument
 * @param {*} b Second argument
 */
var caughtError = null;
function invokeGuardedCallback(name, func, a) {
    try {
        func(a);//直接实行回调要领
    } catch (x) {
        if (caughtError === null) {
            caughtError = x;
        }
    }
}

var ReactErrorUtils = {
    invokeGuardedCallback: invokeGuardedCallback,
    invokeGuardedCallbackWithCatch: invokeGuardedCallback,
    rethrowCaughtError: function rethrowCaughtError() {
        if (caughtError) {
            var error = caughtError;
            caughtError = null;
            throw error;
        }
    }
};

if (process.env.NODE_ENV !== 'production') {//非临盆环境会经由过程自定义事宜去触发还调
    if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function' && typeof document !== 'undefined' && typeof document.createEvent === 'function') {
        var fakeNode = document.createElement('react');

        ReactErrorUtils.invokeGuardedCallback = function (name, func, a) {
            var boundFunc = func.bind(null, a);
            var evtType = 'react-' + name;
            fakeNode.addEventListener(evtType, boundFunc, false);
            var evt = document.createEvent('Event');
            evt.initEvent(evtType, false, false);
            fakeNode.dispatchEvent(evt);
            fakeNode.removeEventListener(evtType, boundFunc, false);
        };
    }
}

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

末了react 经由过程天生了一个暂时节点fakeNode,然后为这个暂时元素绑定事宜处置惩罚顺序,然后建立自定义事宜 Event,经由过程fakeNode.dispatchEvent要领来触发事宜,而且触发终了以后马上移除监听事宜。

到这里事宜回调已实行完成,然则也有些疑问,为何在非临盆环境须要经由过程自定义事宜来实行回调要领。可以看下上面的代码在非临盆环境对ReactErrorUtils.invokeGuardedCallback 要领举行了重写。

5、总结

本文重如果从团体流程上引见了下 react 事宜触发的历程。

重要流程有:

  1. 进入一致的事宜分发函数(dispatchEvent)
  2. 连系原生事宜找到当前节点对应的ReactDOMComponent对象
  3. 举行事宜的合成

3.1 依据当前事宜范例天生指定的合成对象

3.2 封装原生事宜和冒泡机制

3.3 查找当前节点以及他的一切父级

3.4 在listenerBank查找事宜回调并合成到 event(事宜合成终了)

4.批量处置惩罚合成事宜内的回调事宜(事宜触发完成 end)

个中并没有深切到源码的细节,包含事务处置惩罚、合成的细节等,别的梳理历程当中本身也有一些迷惑的处所,对源码有兴致的小伙儿可以深切研讨下,固然照样愿望本文可以带给你一些启示,若文章有表述不清或有题目的处所迎接留言交换。

更多精彩内容迎接关注我的民众号 – 前端张大胖

《连系源码完全明白 react事宜机制道理 04 - 事宜实行》

    原文作者:前端张大胖
    原文地址: https://segmentfault.com/a/1190000019387640
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞