Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 和 JS 事件监听混用问题分析(React 阻止事件冒泡失效、stopPropagation和stopImmediatePropagation分析,解决stopPropagation没有阻止冒泡问题) #2

Open
wqhui opened this issue Jul 7, 2020 · 0 comments

Comments

@wqhui
Copy link
Owner

wqhui commented Jul 7, 2020

前言

做开发中,React 和 JS 原生事件监听addEventListener混用了,因此发现了一个现象:React的阻止冒泡并没有阻止原生JS监听的事件触发。
例子中:onClik阻止冒泡事件并没有生效,仅仅只是阻止outClick,我的需求是只触发inner dom click

代码链接:https://codepen.io/Lik_Lit/pen/OJLROMW

例子中有4个click监听:

  1. React 组件内button元素监听innerClick ,也是点击事件的触发者
  2. React 组件内button父元素监听outClick
  3. React 组件渲染根节点的元素监听root click,注意是原生的监听
  4. document的监听document click,注意是原生的监听
const domContainer = document.querySelector('#root')

// 绑定在外层的点击 非documemt层的原生事件
domContainer.addEventListener('click', e => console.log('root click'))

class InnerDom extends React.Component {
  componentDidMount () {
    // documemt层的原生事件
    document.addEventListener('click', () => {
      console.log('document click')
    })
  }

  outClick = (e) => {
    console.log('out dom click')
  }

  innerClick = (e) => {
    e.stopPropagation();
    console.log('inner dom click')
  }

  render () {
    return <div onClick={this.outClick}>
      <button onClick={this.innerClick}> 测试冒泡</button>
    </div>
  }
}

ReactDOM.render(<InnerDom />, domContainer)

猜猜输出是什么?

"root click"
"inner dom click"
"document click"

解决过程与坑

原来 React 的组件不是挂载到对应元素上的,而且被统一管理起来的合成事件,本质上所有的事件绑定是代理到document上的,然后再触发的时候再根据不同元素去分发执行。类似下面:

// react所有合成事件, SyntheticEvent里面执行回调函数
document.addEventListener('click' SyntheticEvent);

// 浏览器原生
document.addEventListener('click', () => {
   alert('document click');
})

所以在 React 绑定的合成事件调用e.stopPropagation()阻止的只是React的合成事件(eg:<div onClick={()=>{}}/>),而对于原生事件还是阻止不了

百度告诉我说e.nativeEvent.stopImmediatePropagation可以阻止冒泡到原生事件,我就在在innerClick事件里加上了这句,输出变成了

"root click"
"inner dom click"

可以看到还是不行,因为stopImmediatePropagation只是阻止同层级且绑定靠后的事件(具体参考MDN),这里只阻止了document层的事件,同时也验证了上面说的本质上所有的事件绑定是代理到document上的的说法。

那非document层的原生事件我们要怎么阻止呢?我这边只能想到笨方法:React要阻止冒泡到原生事件只有使用原生的绑定去调用e.stopPropagation()

  refCb = (dom) => {
      //这里我就没有去解绑了
      dom && dom.addEventListener('click',this.innerClick)
  }
  <button ref={this.refCb} > 测试冒泡</button>

这样就可以解决了

结论

  1. React 中JSX写法绑定的事件都是合成事件,它们本质上是代理到document的事件,这就相当于事件委托,当某个事件触发时,该事件会冒泡到document上面,React监听到这次事件开始统一分发合成事件给监听的回调函数处理(可以理解成React所有事件都是绑定在document层,React V17之后是绑定到root节点层)
  2. 对于React的合成事件对象e,e.stopPropagation()只能阻止React合成事件的冒泡,e.nativeEvent.stopImmediatePropagation 只能用来阻止冒泡到直接绑定在document上的事件
  3. 如果代码中存在JSX写法绑定的事件和addEventListener事件,要想阻止底层事件冒泡到二者,只有通过原生事件对象e.stopPropagation()去阻止冒泡
@wqhui wqhui changed the title React 阻止事件冒泡失效、stopPropagation和stopImmediatePropagation分析,解决stopPropagation没有阻止冒泡问题 React 和 JS 事件监听混用问题分析 Apr 4, 2021
@wqhui wqhui changed the title React 和 JS 事件监听混用问题分析 React 和 JS 事件监听混用问题分析(React 阻止事件冒泡失效、stopPropagation和stopImmediatePropagation分析,解决stopPropagation没有阻止冒泡问题) Apr 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant