wuxianqiang / blog

博客系列
17 stars 4 forks source link

react合成事件分析 #277

Open wuxianqiang opened 4 years ago

wuxianqiang commented 4 years ago

先写一个事件代理的方法,这个delegate方法可以给子元素添加和移除绑定事件处理函数。

<body>
  <div>
    <ul id="app">
      <li class="childClassname">11</li>
      <li class="childClassname">11</li>
    </ul>
  </div>

  <script>
    let parentElement = document.getElementById('app')
    function callback (e) {
      console.log('ok', e)
    }
    delegate(parentElement).on('childClassname', 'click', callback);
    delegate(parentElement).off('childClassname', 'click', callback);

    function delegate (parentElement) {
      function on (childClassname, eventType, listener) {
        let list = parentElement.getElementsByClassName(childClassname)
        Array.from(list, (dom) => {
          let eventStore = dom.eventStore || (dom.eventStore = {})
          eventStore[eventType] = listener;
        })
        document.addEventListener(eventType, dispatchEvent, false)
      }
      function dispatchEvent (event) {
        let { type, target } = event
        let eventType = type;
        while (target) {
          let { eventStore } = target;
          let listener = eventStore && eventStore[eventType]
          if (listener) {
            listener.call(target, event)
          }
          target = target.parentNode;
        }
      }
      function off (childClassname, eventType, listener) {
        let list = parentElement.getElementsByClassName(childClassname)
        Array.from(list, (dom) => {
          let eventStore = dom.eventStore
          let listener = eventStore && eventStore[eventType]
          if (listener) {
            eventStore[eventType] = null
          }
        })
      }
      return {on, off}
    }

  </script>
</body>

现在你已经明白了如何将子元素绑定的事件都代理到document对象,下面继续深入学习react的绑定事件。

let onClick = () => { console.log('hello') }
let element = React.createElement(
  'button',
  { id: 'sayHello', onClick },
  'say',
  React.createElement('span', { color: 'red' }, 'hello'))

React在渲染DOM的时候会做事件绑定。

  1. 给绑定DOM的节点挂载一个对象,保存事件名称和事件处理函数
    function addEvent(dom, eventType, listener) {
      eventType = eventType.toLowerCase()
      // 给绑定DOM的节点挂载一个对象
      let eventStore = dom.eventStore || (dom.eventStore = {});
      // 对象上保存事件名称和事件处理函数{onclick: listener}
      eventStore[eventType] = listener
      document.addEventListener(eventType.slice(2), dispatchEvent, false)
    }
  2. 给DOM绑定监听,函数是dispatchEvent
    let syntheticEvent;
    function dispatchEvent(event) {
      // 此时event是原生事件对象
      let { target, type } = event;
      // 在事件对象中获取事件源和事件名
      let eventType = 'on' + type;
      // 获取合成事件对象
      syntheticEvent = getSyntheticEvent(event)
      while (target) {
        let { eventStore } = target;
        let listener = eventStore && eventStore[eventType]
        if (listener) {
          listener.call(target, syntheticEvent)
        }
        target = target.parentNode;
      }
      // 合成事件用完会清空,如果不想清空需要使用persist函数
      for (let key in syntheticEvent) {
        if (key != 'persist') {
          syntheticEvent[key] = null
        }
      }
    }
  3. 然后创建了一个合成事件对象,合成事件对象会把原始事件的属性和方法都复制过来
    function persist() {
      syntheticEvent = {persist}
    }
    function getSyntheticEvent(nativeEvent) {
      if (!syntheticEvent) {
        syntheticEvent = {persist}
      }
      syntheticEvent.nativeEvent = nativeEvent;
      syntheticEvent.currentTarget = nativeEvent.target;
      // 把原生事件对象的属性和方法都复制到合成事件对象中
      for (let key in nativeEvent) {
        if (typeof nativeEvent[key] === 'function') {
          syntheticEvent[key] = nativeEvent[key].bind(nativeEvent)
        } else {
          syntheticEvent[key] = nativeEvent[key]
        }
      }
      return syntheticEvent
    }
  4. 开始使用

    let app = document.getElementById('app')
    function callback (e) {
      e.persist()
      console.log('ok', e)
    }
    addEvent(app, 'onClick', callback)

    完整代码

    <script>
    let app = document.getElementById('app')
    function callback (e) {
      e.persist()
      console.log('ok', e)
    }
    addEvent(app, 'onClick', callback)
    
    function addEvent(dom, eventType, listener) {
      eventType = eventType.toLowerCase()
      // 给绑定DOM的节点挂载一个对象
      let eventStore = dom.eventStore || (dom.eventStore = {});
      // 对象上保存事件名称和事件处理函数{onclick: listener}
      eventStore[eventType] = listener
      document.addEventListener(eventType.slice(2), dispatchEvent, false)
    }
    let syntheticEvent;
    function dispatchEvent(event) {
      // 此时event是原生事件对象
      let { target, type } = event;
      // 在事件对象中获取事件源和事件名
      let eventType = 'on' + type;
      // 获取合成事件对象
      syntheticEvent = getSyntheticEvent(event)
      while (target) {
        // 从事件源开始冒泡
        let { eventStore } = target;
        let listener = eventStore && eventStore[eventType]
        if (listener) {
          listener.call(target, syntheticEvent)
        }
        target = target.parentNode;
      }
      // 完成之后清空syntheticEvent
      for (let key in syntheticEvent) {
        if (key != 'persist') {
          syntheticEvent[key] = null
        }
      }
    }
    function persist() {
      syntheticEvent = {persist}
    }
    function getSyntheticEvent(nativeEvent) {
      if (!syntheticEvent) {
        syntheticEvent = {persist}
      }
      syntheticEvent.nativeEvent = nativeEvent;
      syntheticEvent.currentTarget = nativeEvent.target;
      // 把原生事件对象的属性和方法都复制到合成事件对象中
      for (let key in nativeEvent) {
        if (typeof nativeEvent[key] === 'function') {
          syntheticEvent[key] = nativeEvent[key].bind(nativeEvent)
        } else {
          syntheticEvent[key] = nativeEvent[key]
        }
      }
      return syntheticEvent
    }
    </script>
    </body>

持久化之后下次会创建一个新的对象,旧的对象也不会销毁,两个对象互不干扰 关于persist可以参考官方文档链接