媒介
这是 react 事宜机制的第四节-事宜实行,一同研讨下在这个历程当中重要经由了哪些关键步骤,本文也是react 事宜机制的完结篇,愿望本文可以让你对 react 事宜实行的道理有肯定的邃晓。
文章涉及到的源码是基于 react15.6.1版本,虽然不是最新版本然则也不会影响我们对 react 事宜机制的团体把握和邃晓。
回忆
先简朴的回忆下上一文,事宜注册的效果是是把一切的事宜回调保留到了一个对象中
那末在事宜触发的历程当中上面这个对象有什么用途呢?
实在就是用来查找事宜回调。
内容纲要
依据我的邃晓,事宜触发历程总结为重要下面几个步骤
1.进入一致的事宜分发函数(dispatchEvent)
2.连系原生事宜找到当前节点对应的ReactDOMComponent对象
3.举行事宜的合成
3.1依据当前事宜范例天生指定的合成对象
3.2封装原生事宜和冒泡机制
3.3查找当前节点以及他的一切父级
3.4在listenerBank查找事宜回调并合成到 event(合成事宜终了)
4.批量处置惩罚合成事宜内的回调事宜(事宜触发完成 end)
说再多不如配个图
举个栗子
在说详细的流程前,先看一个栗子,背面的剖析也是基于这个栗子
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的事宜。
1、进入一致的事宜分发函数 (dispatchEvent)
当我点击child div 的时刻,这个时刻浏览器会捕捉到这个事宜,然后经由冒泡,事宜被冒泡到 document 上,交给一致事宜处置惩罚函数 dispatchEvent 举行处置惩罚。(上一文中我们已说过 document 上已注册了一个一致的事宜处置惩罚函数 dispatchEvent)
2、连系原生事宜找到当前节点对应的ReactDOMComponent对象
在原生事宜对象内已保留了对应的ReactDOMComponent实例,应该是在挂载阶段就已保留了
看下ReactDOMComponent实例的内容
3、最先举行事宜合成
事宜的合成,冒泡的处置惩罚以及事宜回调的查找都是在合成阶段完成的。
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 代码就邃晓了
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 长啥样
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];
}
这里要高亮一下
为何可以查找到的呢?
由于 inst (组件实例)里有_rootNodeID,所以也就有了对应关联
到这里事宜合成对象天生完成,一切的事宜回调已保留到了合成对象中。
4、 批量处置惩罚合成事宜对象内的回调要领(事宜触发完成 end)
第3步天生完 合成事宜对象后,挪用栈回到了我们早先实行的要领内
//在这里实行事宜的回调
runEventQueueInBatch(events);
到下面这一步中心省略了一些代码,只贴出重要的代码,
下面要领会轮回处置惩罚 合成事宜内的回调要领,同时推断是不是制止事宜冒泡。
贴上末了的实行回调要领的代码
/**
*
* @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 经由过程天生了一个暂时节点fakeNode,然后为这个暂时元素绑定事宜处置惩罚顺序,然后建立自定义事宜 Event,经由过程fakeNode.dispatchEvent要领来触发事宜,而且触发终了以后马上移除监听事宜。
到这里事宜回调已实行完成,然则也有些疑问,为何在非临盆环境须要经由过程自定义事宜来实行回调要领。可以看下上面的代码在非临盆环境对ReactErrorUtils.invokeGuardedCallback 要领举行了重写。
5、总结
本文重如果从团体流程上引见了下 react 事宜触发的历程。
重要流程有:
- 进入一致的事宜分发函数(dispatchEvent)
- 连系原生事宜找到当前节点对应的ReactDOMComponent对象
- 举行事宜的合成
3.1 依据当前事宜范例天生指定的合成对象
3.2 封装原生事宜和冒泡机制
3.3 查找当前节点以及他的一切父级
3.4 在listenerBank查找事宜回调并合成到 event(事宜合成终了)
4.批量处置惩罚合成事宜内的回调事宜(事宜触发完成 end)
个中并没有深切到源码的细节,包含事务处置惩罚、合成的细节等,别的梳理历程当中本身也有一些迷惑的处所,对源码有兴致的小伙儿可以深切研讨下,固然照样愿望本文可以带给你一些启示,若文章有表述不清或有题目的处所迎接留言交换。
更多精彩内容迎接关注我的民众号 – 前端张大胖