创建 event 对象

在触发事件的过程中,我们会调用每个插件的extractEvents方法来创建对应的事件,这里我们就看看事件创建的过程。这里我们拿最常用的事件之一onChange来举例,主要看的是ChangeEventPlugin.js

根据不同的情况设置getTargetInstFunc

function shouldUseChangeEvent(elem) {
  const nodeName = elem.nodeName && elem.nodeName.toLowerCase()
  return nodeName === 'select' || (nodeName === 'input' && elem.type === 'file')
}

isTextInputElement判断input标签的type是否合理,如果是textarea则直接返回true

function shouldUseClickEvent(elem) {
  const nodeName = elem.nodeName
  return (
    nodeName &&
    nodeName.toLowerCase() === 'input' &&
    (elem.type === 'checkbox' || elem.type === 'radio')
  )
}

对于checkboxradio使用click监听

这些方法的区别就是判断本次事件的具体类型不同,最终调用的都是getInstIfValueChanged

function getInstIfValueChanged(targetInst) {
  const targetNode = getNodeFromInstance(targetInst)
  if (inputValueTracking.updateValueIfChanged(targetNode)) {
    return targetInst
  }
}

使用track判断input的值有没有变化,如果有变化则则返回,没有不返回,也就不需要生成事件

extractEvents = function(
  topLevelType,
  targetInst,
  nativeEvent,
  nativeEventTarget,
) {
  const targetNode = targetInst ? getNodeFromInstance(targetInst) : window

  let 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) {
    const inst = getTargetInstFunc(topLevelType, targetInst)
    if (inst) {
      const 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)
  }
}

function getTargetInstForChangeEvent(topLevelType, targetInst) {
  if (topLevelType === TOP_CHANGE) {
    return targetInst
  }
}

function getTargetInstForInputOrChangeEvent(topLevelType, targetInst) {
  if (topLevelType === TOP_INPUT || topLevelType === TOP_CHANGE) {
    return getInstIfValueChanged(targetInst)
  }
}

function getTargetInstForClickEvent(topLevelType, targetInst) {
  if (topLevelType === TOP_CLICK) {
    return getInstIfValueChanged(targetInst)
  }
}

createAndAccumulateChangeEvent

从这里开始构建事件,首先 React 的事件有一个pool,可以复用事件对象,不需要每次都重新创建,然后调用accumulateTwoPhaseDispatches开始为事件对象挂载两个阶段的监听者:

  • 捕获阶段
  • 冒泡阶段

forEachAccumulated跟调用事件的时候一样,其实就是为每个事件调用accumulateTwoPhaseDispatchesSingle

traverseTwoPhase向上遍历树找到所有HostComponent,并对每一个节点调用accumulateDirectionalDispatcheslistenerAtPhase代码如下:

function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
  const registrationName =
    event.dispatchConfig.phasedRegistrationNames[propagationPhase]
  return getListener(inst, registrationName)
}

export function getListener(inst: Fiber, registrationName: string) {
  let listener

  const stateNode = inst.stateNode
  if (!stateNode) {
    return null
  }
  const props = getFiberCurrentPropsFromNode(stateNode)
  if (!props) {
    return null
  }
  listener = props[registrationName]
  if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
    return null
  }
  // warn
  return listener
}

通过对HostComponentFiber对象上获取props,并判断时候有事件监听的props,比如onChangeonChangeCapture,如果有就返回处理函数。在accumulateDirectionalDispatches就会赋值在

在这里并没有区分不同阶段的事件,但是在放到_dispatchListeners里面的过程中,会直接安排好顺序,注意traverseTwoPhase中的两个遍历的顺序,第一个是反向的,也就是从最顶点的节点开始。通过这样来保证事件触发是按照顺序来的。

function createAndAccumulateChangeEvent(inst, nativeEvent, target) {
  const event = SyntheticEvent.getPooled(
    eventTypes.change,
    inst,
    nativeEvent,
    target,
  )
  event.type = 'change'

  // controlled input fallback
  enqueueStateRestore(target)
  accumulateTwoPhaseDispatches(event)
  return event
}

export function accumulateTwoPhaseDispatches(events) {
  forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle)
}

function accumulateTwoPhaseDispatchesSingle(event) {
  if (event && event.dispatchConfig.phasedRegistrationNames) {
    traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event)
  }
}

export function traverseTwoPhase(inst, fn, arg) {
  const path = []
  while (inst) {
    path.push(inst)
    inst = getParent(inst)
  }
  let i
  for (i = path.length; i-- > 0; ) {
    fn(path[i], 'captured', arg)
  }
  for (i = 0; i < path.length; i++) {
    fn(path[i], 'bubbled', arg)
  }
}

function accumulateDirectionalDispatches(inst, phase, event) {
  const listener = listenerAtPhase(inst, event, phase)
  if (listener) {
    event._dispatchListeners = accumulateInto(
      event._dispatchListeners,
      listener,
    )
    event._dispatchInstances = accumulateInto(event._dispatchInstances, inst)
  }
}

results matching ""

    No results matching ""

    Jokcy的二维码

    扫码添加Jokcy,更多更新更优质的前端学习内容不断更新中,期待与你一起成长!

    Jokcy的二维码