上节说到,会把 dispatch()
绑定到原生DOM
上,每次触发事件时,调用的都是dispatch
函数
function dispatchEvent(topLevelType: TopLevelType, nativeEvent: AnyNativeEvent) {
if (!_enabled) {
return
}
const nativeEventTarget = getEventTarget(nativeEvent)
let targetInst: Fiber = getClosestInstanceFromNode(nativeEventTarget)
if (targetInst !== null && isNumber(targetInst.tag) && !isFiberMounted(targetInst)) {
targetInst = null
}
const bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst)
try {
batchedUpdates(handleTopLevel, bookKeeping)
} catch (e) {
console.error(e)
releaseTopLevelCallbackBookKeeping(bookKeeping)
}
}
先通过event
对象拿到target
和相应的fiber
实例,然后封装成一个bookKeeping
对象,这里同样使用了对象池来优化创建的过程,在getTopLevelCallbackBookKeeping
中根据对象池的大小取出或者新建对象,在事件完成后使用releaseTopLevelCallbackBookKeeping
释放对象并放回到事件池中
关键在于这里的batchedUpdates
,这个函数位于schedule
中,是react
里实现异步setState
的核心所在。在执行事件前,将isBatchingUpdates
这个标志位设为了true。这个标志位在requestWork
中有用到
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
addRootToSchedule(root, expirationTime)
if (isRendering) {
return
}
if (isBatchingUpdates) {
if (isUnbatchingUpdates) {
nextFlushedRoot = root
nextFlushedExpirationTime = Sync
performWorkOnRoot(root, Sync, false)
}
return
}
if (expirationTime === Sync) {
performSyncWork() // 同步
} else {
scheduleCallbackWithExpirationTime(expirationTime) // 异步
}
}
回顾requestWork
,如果isBatchingUptes
为true
的话,就直接中断掉,不往下继续执行。所以渲染在事件函数没执行完之前都不会进行,一直到整个事件函数都结束掉,在finally
中重启整个任务,所以如果在同一个事件监听函数中多次setState
,则只是将它们放到updateQueue
的末尾。React
通过这个异步机制避免了频繁setState
触发渲染造成的性能浪费
function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
const previousIsBatchingUpdates = isBatchingUpdates
isBatchingUpdates = true
try {
return fn(a)
} finally {
isBatchingUpdates = previousIsBatchingUpdates
if (!isBatchingUpdates && !isRendering) {
performSyncWork()
}
}
}
由于事件函数中可能会操作DOM
,导致与初始渲染时的节点缓存不一致,所以在进入核心逻辑之前,先构建一个关于ancestor
数组,防止任何的嵌套组件导致的bug
。构建完ancestor
数组后,我们可以利用现有的信息去生成event
function handleTopLevel(bookKeeping: BookKeeping) {
let targetInst: Fiber = bookKeeping.targetInst
let ancestor: Fiber = targetInst
do {
if (!ancestor) {
bookKeeping.ancestors.push(ancestor)
break
}
const root = findRootContainerNode(ancestor)
if (!root) {
break
}
bookKeeping.ancestors.push(ancestor)
ancestor = getClosestInstanceFromNode(root)
} while (ancestor)
bookKeeping.ancestors.forEach((target: Fiber) => {
targetInst = target
runExtractedEventsInBatch(bookKeeping.topLevelType, targetInst, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent))
})
}
前面介绍plugin
时提到过,每个plugin
都有着统一的extractEvents
函数,用于生成SyntheticEvent
整个获取SyntheticEvent
的过程也就是遍历已经inject
的plugin
,调用它们各自的extractEvents
拿到SyntheticEvent
,那么,extractEvents
是如何生成event
的呢?具体的逻辑如下:
- 通过传入的事件名,选择对应的具体的
SyntheticEvent
子类,如touch
事件对应的SyntheticEvent
是SyntheticTouchEvent
- 通过事件池拿到
event
对象 - 模拟冒泡、捕获的过程,按照顺序取出
props
的事件监听函数及相应的 fiber ,绑定到event
的 _dispatchListeners 和 _dispatchInstances 上 - 返回
event
在获得SyntheticEvent
的过程中,plugin
的extractEvents
都调用了accumulateTwoPhaseDispatches
对冒泡、捕获做了处理,如图
function accumulateTwoPhaseDispatches(events: SyntheticEvent) {
function callback(event: SyntheticEvent) {
if (event && event.dispatchConfig.phasedRegistrationNames) {
traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event) // 捕获和冒泡
}
}
forEachAccumulated(events, callback)
}
export function traverseTwoPhase(inst: Fiber, fn: Function, arg: SyntheticEvent) {
const path = []
while (inst) {
path.push(inst)
inst = getParent(inst)
}
let i: number
for (i = path.length; i-- > 0;) {
fn(path[i], 'captured', arg)
}
for (i = 0; i < path.length; i++) {
fn(path[i], 'bubbled', arg)
}
}
在traverseTwoPhase
中对当前fiber
往上遍历直到fiberRoot
,并存储到path
中,之后按照 捕获->冒泡 的顺序对每个path
的fiber
调用accumulateDirectionalDispatches
function accumulateDirectionalDispatches(inst: Fiber, phase: Phases, event: SyntheticEvent) {
const listener = listenAtPhase(inst, event, phase)
if (listener) {
event._dispatchListeners = accumulateInto(event._dispatchListeners, listener)
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst)
}
}
在这个函数中,根据传入的参数,拿到fiber props
的事件处理函数和相应的fiber
,并挂载到event
的_dispatchListeners
和_dispatchInstances
上
function getListener(inst: Fiber, registrationName: string): Function {
let listener: Function = null
const { stateNode } = inst
if (!stateNode) {
return null
}
const props = getFiberCurrentPropsFromNode(stateNode)
if (!props) {
return null
}
listener = props[registrationName]
if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
return null
}
return listener
}
function listenAtPhase(inst: Fiber, event: SyntheticEvent, phase: Phases) {
const registrationName = event.dispatchConfig.phasedRegistrationNames[phase]
return getListener(inst, registrationName)
}
由于在生成event
时已经把props
里跟事件有关的信息注册到event
的_dispatchListeners
和_dispatchInstances
上,所以,我们需要遍历_dispatchListeners
,执行相应的事件函数listener(event)
,由于这里是直接执行,所以在我们写的事件函数中,如果使用了this
且没有强绑定,在调用时会拿不到对应的this
function executeDispatchesInOrder(event: SyntheticEvent) {
const dispatchListeners = event._dispatchListeners
const dispatchInstances = event._dispatchInstances
if (Array.isArray(dispatchListeners)) {
for (let i = 0; i < dispatchListeners.length; i++) {
// 判断是否停止冒泡
if (event.isPropagationStopped()) {
break
}
executeDispatch(event, dispatchListeners[i], dispatchInstances[i])
}
} else if (dispatchListeners) {
executeDispatch(event, dispatchListeners, dispatchInstances as Fiber)
}
event._dispatchListeners = null
event._dispatchInstances = null
}
function executeDispatch(event: SyntheticEvent, listener: Function, inst: Fiber) {
event.currentTarget = getNodeFromInstance(inst)
listener(event)
event.currentTarget = null
}
export {
executeDispatchesInOrder,
}
到这里,整个事件的触发过程就结束了,回到batchedUpdates
的finally
,把相应的标记位置回false
,开始渲染