react事件

86 阅读3分钟

事件

  • react采用了合成事件
  • 简述:如我们写的onClick并没有绑定到真实dom上,而是属于fiber的属性,而事件是具有传播性的(冒泡和捕获),那么我们在根容器上绑定一个冒泡和捕获,然后按照执行的顺序去遍历fiber,拿到它属性中对应的on事件,然后给事件包装下执行即可。
  • 可以统一解决浏览器兼容性并充分使用了事件委托机制

绑定

  • 在crateRoot阶段进行的根容器绑定
  • 通过bind,将前几个值固定,后续只是灵活的去处理事件对象
// 最终注册给root的事件为
dispatchDiscreteEvent.bind(null, domEventName, eventSystemFlags, targetContainer);

/**
 * 派发离散的事件的的监听函数
 * @param {*} domEventName 事件名 如:click
 * @param {*} eventSystemFlags 阶段 0 冒泡 4 捕获
 * @param {*} container 容器div#root
 * @param {*} nativeEvent 原生的事件
 */
function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent) {
  dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
}
  • 怎么绑定呢
// 事件表
let allNativeEvents = new Set(['click'......]);
allNativeEvents.forEach((domEventName) => {
    // rootContainerElement为div#root
    // true为是否为捕获阶段
    listenToNativeEvent(domEventName, true, rootContainerElement);
    listenToNativeEvent(domEventName, false, rootContainerElement);
});

事件触发

  • 执行dispatchEvent
  • 获取target,通过多种形式去取,避免浏览器差异
/**
 * nativeEvent为原生事件
 */
function getEventTarget(nativeEvent) {
  const target = nativeEvent.target || nativeEvent.srcElement || window;
  return target;
}
  • 然后通过真实dom上的标识拿到fiber
    • 在fiber生成真实dom的阶段,会将fiber通过一个标识,存储到真实dom中,为的是方便获取
    • 这个key是一个动态的值,每次刷新页面都会重新生成,且存在内存中
    • props也是相同的操作,也通过一个唯一标识存储在真实dom中
// 随机数
const randomKey = Math.random().toString(36).slice(2);
// fiber的唯一标识
const internalInstanceKey = '__reactFiber$' + randomKey;
// props的唯一标识
const internalPropsKey = "__reactProps$" + randomKey;

// 存储props
export function updateFiberProps(node, props) {
  node[internalPropsKey] = props;
}
// 获取props
export function getFiberCurrentPropsFromNode(node) {
  return node[internalPropsKey] || null;
}
// 存储fiber
export function precacheFiberNode(hostInst, node) {
  node[internalInstanceKey] = hostInst;
}
  • 然后通过fiber开始向上遍历,拿到对应的props中的对应事件和fiber的stateNode
  • 先存起来,然后找完以后,处理事件源
    • 将事件源的原始信息拷贝下,然后添加一些新的属性
    • 并且在原型上处理preventDefault和stopPropagation事件,实现跨平台
// 处理事件源
const assion = Object.assion;

function functionThatReturnsTrue() {
  return true;
}
function functionThatReturnsFalse() {
  return false;
}
const MouseEventInterface = {
  clientX: 0,
  clientY: 0
}

function createSyntheticEvent(inter) {
  /**
   *合成事件的基类
   * @param {*} reactName React属性名 onClick
   * @param {*} reactEventType click
   * @param {*} targetInst 事件源对应的fiber实例
   * @param {*} nativeEvent 原生事件对象
   * @param {*} nativeEventTarget 原生事件源 span 事件源对应的那个真实DOM
   */
  function SyntheticBaseEvent(
    reactName, reactEventType, targetInst, nativeEvent, nativeEventTarget) {
    this._reactName = reactName;
    this.type = reactEventType;
    this._targetInst = targetInst;
    this.nativeEvent = nativeEvent;
    this.target = nativeEventTarget;
    //把此接口上对应的属性从原生事件上拷贝到合成事件实例上
    for (const propName in inter) {
      if (!inter.hasOwnProperty(propName)) {
        continue;
      }
      this[propName] = nativeEvent[propName]
    }
    //是否已经阻止默认事件
    this.isDefaultPrevented = functionThatReturnsFalse;
    //是否已经阻止继续传播
    this.isPropagationStopped = functionThatReturnsFalse;
    return this;
  }
  assign(SyntheticBaseEvent.prototype, {
    preventDefault() {
      const event = this.nativeEvent;
      if (event.preventDefault) {
        event.preventDefault();
      } else {
        event.returnValue = false;
      }
      this.isDefaultPrevented = functionThatReturnsTrue;
    },
    stopPropagation() {
      const event = this.nativeEvent;
      if (event.stopPropagation) {
        event.stopPropagation();
      } else {
        event.cancelBubble = true;
      }
      this.isPropagationStopped = functionThatReturnsTrue;
    }
  });
  return SyntheticBaseEvent;
}
export const SyntheticMouseEvent = createSyntheticEvent(MouseEventInterface);
  • 开始依次是在收集到的函数,每次执行前,先将stateNode给到event.currentTarget,所以它是在实时变化的,并且将处理好的事件源传给函数
  • 阻止冒泡和捕获因为包装了一层,所以可以在实例上拿到是否阻止,如果阻止就break,不执行后续操作事件了