今天看啥  ›  专栏  ›  牧羊童鞋

react事件机制和原生dom事件机制之男女有别

牧羊童鞋  · 简书  ·  · 2017-12-08 15:13

今天遇到这么一个问题:

我们在实现列表拖拽排序时使用了react-sortable-hoc这个库,确实很好用,利用ES装饰器新特性,几个@符号就解决了。但是因为我们在每一个列表item上还有菜单按钮,点击菜单按钮会弹出菜单,这样一来就会发现菜单的click事件和react-sortable-hocmousedown事件冲突了,特别是在你把pressDelay设置低于300ms时。

如果你使用过这个组件,你会发现pressDelay超过100ms,拖住体验就不再有丝滑般的感觉了。怎么办?我们需要完美啊,既要丝滑般的感觉又不要事件冲突。

假设在jQuery一把鎍的时代,这个都不是问题,因为菜单按键和列表item明显是父子元素的关系,我只需要把菜单上的mouse事件不向父元素listitem冒泡就解决了。

事实上时代并没有变化,react时代也是这么解决的。只是解决的时候会发现一个细节需要注意,那就是--react事件机制和原生dom事件机制之男女有别

我们先来看react-sortable-hoc组件源码里是怎么绑定事件的

_utils.events.move.forEach(function (eventName) {
  // 原生事件addEventListener
   return _this.listenerNode.addEventListener(eventName, 
   _this.handleSortMove, false);
});
_utils.events.end.forEach(function (eventName) {
  return _this.listenerNode.addEventListener(eventName, _this.handleSortEnd, false);
});

使用的是dom的addEventListener函数绑定事件,这走的是原生事件机制。

然后看我们在react项目中怎么去stop冒泡的

// 简单期间,只贴render函数足以 
render() {
    const { showMenu } = this.state;
    const { children, className } = this.props;
    const cls = classNames(styles['dropdown'], className);
    return (
      <div
        className={cls}
        ref={(el) => {
          this.dropDown = el;
        }}
        { // 这里直接绑定onMouseDown,走的是react的SyntheticEvent机制 }
        onMouseDown={ (e) => e.stopPropagation()} 
      >
        { this.renderDropIcon() }
        <Animate transitionName='fade' component='span'>
          { showMenu && this.renderDropDownMenu()}
        </Animate>
      </div>
    );
  }

在jsx模板里直接绑定onMouseDown/onMouseMove/onMouseUp事件,并调用e.stopPropagation()企图阻止冒泡。

然后你就发现完全没卵用啊,没道理啊,怎么会没有用呢?抓耳中...

这时候如果你在两个地方均加上log,你会发现竟然是react-sortable-hoc包裹的列表item的mousedown事件先触发,作为子元素的菜单后触发,更郁闷了有么有?

其实你通过本文的题目应该就猜到原因了。根本原因就是react的事件机制自成一套,具体可以看这篇文章React事件机制
](https://segmentfault.com/a/1190000008782645)

主要一个观点就是:

其实React事件并没有原生的绑定在真实的DOM上,而是使用了行为委托方式实现事件机制。
React会将所有的事件都绑定在最外层(document),使用统一的事件监听,并在冒泡阶段处理事件,当挂载或者卸载组件时,只需要在通过的在统一的事件监听位置增加或者删除对象,因此可以提高效率。

明白了吧,我们直接在jsx模板上绑定的事件,都是委托在了document上,那自然要比直接在dom上绑定的事件慢了,等document收到事件后才去e.stopPropagation(),太晚了

所以解决方案就是通过ref获取到真实的dom,在componentDidMount时通过addEventListener添加事件监听,此时阻止冒泡就好了。注意一点,这种方式需要自己释放事件,即在componentWillUnmount时removeEventListener。

每一次遇到问题都会让自己对一些理论有更深的理解和记忆,还是那句话:问题是最好的教材啊




原文地址:访问原文地址
快照地址: 访问文章快照