react事件机制

1. react的事件是合成事件((Synethic event),不是原生事件

<button onClick={this.handleClick}></button>

<input value={this.state.name} onChange={this.handleChange} />

合成事件与原生事件的区别

1. 写法不同,合适事件是驼峰写法,而原生事件是全部小写
2. 执行时机不同,合适事件全部委托到document上,而原生事件绑定到DOM元素本身
3. 合成事件中可以是任何类型,比如this.handleClick这个函数,而原生事件中只能是字符串

2. react合成事件执行过程

3. 事件绑定this写法

React的ES5写法,事件绑定可以自动绑定到组件实例上,而ES6写法却不能,必须使用一些特定的写法。

1. 构造函数bind this

class EventApp extends Component {
    constructor(props){
        super(props) ;
        this._clickMe = this._clickMe.bind(this) ;
    }
    render(){
        return
          (<div>
            <button onClick={this._clickMe}>点击我</button>
          </div>
          ) ; 
    }
    _clickMe(){
        alert("构造方法绑定事件实现方法") ;
   }   
}

只在构造组件时绑定一次,最佳的方式

2. 元素上bind this

class EventApp extends Component {
    render(){
        return
          (<div>
            <button onClick={this._clickMe.bind(this)}>点击我</button>
          </div>
          ) ; 
    }
    _clickMe(){
        alert("组件绑定事件实现方法") ;
    }
}

每次click会重新生成一个绑定函数,效率不佳

3. 使用箭头函数

class EventApp extends Component {
    constructor(props){
        super(props) ;
    }
    render(){
        return
          (<div>
              <button onClick={(e) => this._clickMe(e,"使用箭头函数绑定")}>使用箭头函数绑定事件</button> <p/>
          </div>
          ) ; 
    }
    _clickMe(e,args){
        alert("箭头函数绑定事件实现方法") ;
        alert(args);
        alert(e);
    }
}

这种箭头函数定义在render中,组件每渲染一次都会创建一次新的箭头函数,对性能有影响

4. React组件中使用原生事件

由于原生事件绑定在真实DOM上,所以一般是在componentDidMount中或ref回调函数中进行
绑定操作,在componentWillUnmount阶段进行解绑操作,以避免内存泄漏。

class ReactEvent extends Component {
    componentDidMount() {
        //获取当前真实DOM元素
        const thisDOM = ReactDOM.findDOMNode(this);
        //或者
        const thisDOM = this.refs.con;
        thisDOM.addEventListener('click',this.onDOMClick,false);
    }
    componentWillUnmount() {
        //卸载时解绑事件,防止内存泄漏
        const thisDOM = ReactDOM.findDOMNode(this);
        thisDOM.removeEventListener('click',this.removeDOMClick);
    }
    onDOMClick(e){
        console.log(e)
    }
    render(){
        return(
            <div ref="con">
                单击原始事件触发
            </div>
        )
    }
}
export default ReactEvent

在componentDidMount周期中,使用addEventListener直接绑定即可,dom元素使用ref或者
ReactDOM.findDOMNode来获取。

合成事件和原生事件可以混合使用,但是尽量避免这种情况出现,因为容易导致混乱,则某些情况下
可以使用。合成事件和DOM事件混合使用,触发顺序是:

1. 先执行原生事件,事件冒泡至document,再执行合成事件
2. 如果是父子元素,触发顺序为 子元素原生事件 -> 父元素原生事件 -> 子元素合成事件 -> 父元素合成事件

如下例子:

class ReactEvent extends Component {
    constructor(props){
        super(props)
        this.state = {
            value: ''
        }
        this.onReactClick = this.onReactClick.bind(this)
        this.onReactChildClick = this.onReactChildClick.bind(this)
    }
    componentDidMount() {
        //获取当前真实DOM元素
        const thisDOM = ReactDOM.findDOMNode(this);
        thisDOM.addEventListener('click',this.onDOMClick,false);
        //获取子元素并绑定事件
        const thisDOMChild = thisDOM.querySelector(".child");
        thisDOMChild.addEventListener('click',this.onDOMChildClick,false);
    }
    onDOMClick(e){
        console.log("父组件原生事件绑定事件触发")
    }
 
    onReactClick(e){
        console.log("父组件React合成事件绑定事件触发")
    }
    onDOMChildClick(e){
        e.stopPropagation()
        console.log("子元素原生事件绑定事件触发")
    }
    onReactChildClick(e){
        console.log("子元素React合成事件绑定事件触发")
    }
    render(){
            return(
                <div onClick={this.onReactClick}>                
                    父元素单击事件触发
                    <button className="child" onClick={this.onReactChildClick}>子元素单击事件触发</button>
                </div>
            )
    }
}
export default ReactEvent

通过设置原生事件绑定为冒泡阶段调用,且每次测试单击子元素按钮:

1.在子元素原生事件程序中阻止事件传播,则打印出:

子元素原生事件绑定事件触发;

2.在父元素元素事件程序中阻止事件传播,则打印出:

子元素原生事件绑定事件触发
父组件原生事件绑定事件触发

3.在子元素React合成事件onClick中阻止事件传播,则打印出:

子元素原生事件绑定事件触发
父组件原生事件绑定事件触发
子元素React合成事件绑定事件触发

4.在父元素React合成事件onClick中阻止事件传播,则打印出:

子元素原生事件绑定事件触发
父组件原生事件绑定事件触发
子元素React合成事件绑定事件触发
父组件React合成事件绑定事件触发

可以看到若不阻止事件传播每次(单击子元素)事件触发流程是:
Document->子元素(原生事件触发)->父元素(原生事件)->回到Document->React子元素合成事件监听器触发 ->React父元素合成事件监听器触发

5. 合成事件与阻止冒泡

基本原则

阻止合成事件的冒泡不会阻止原生事件的冒泡,但是阻止原生事件的冒泡会阻止合成事件的冒泡。

1. 阻止合成事件冒泡,用e.stopPropagation()

例如:

render() {
        return 
            <div onClick={this.outClick}>
                <button onClick={this.onClick}> 测试click事件 </button>
            </div>
}

outClick是外层合成事件,用e.stopPropagation会阻止其运行,但是不能阻止原生事件,例如document上用
addEventListener绑定的事件

2. 阻止原生事件和合成事件冒泡(因为阻止了原生事件就会阻止合成事件),用e.nativeEvent.stopImmediatePropagation();
3. 在document或body上注册的原生事件方法,可以通过e.target判断来阻止冒泡事件的执行

例如存在如下的业务场景: 点击input框展示日历,点击文档其他部分,日历消失,代码如下:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      showCalender: false
    };
  }
  componentDidMount() {
    document.addEventListener('click', (e) => {
      var tar = document.getElementById('myInput');
      //判断e.target使得document事件不往下执行
      if (tar.contains(e.target)) return;
      console.log('document!!!');
      this.setState({showCalender: false});
    }, false);
  }
  render() {
    return (<div>
      <input
        id="myInput"
        type="text"
        onClick={(e) => {
          this.setState({showCalender: true});
          console.log('it is button')
          // e.stopPropagation();
        }}
      />
      <Calendar isShow={this.state.showCalender}></Calendar>
    </div>);
  }
}

其他的处理方法,也就是input也使用原生事件来阻止冒泡,或者使用stopImmediatePropagation

7. 合成事件执行过程

React不会将事件处理函数直接绑定到真实的节点上,而是把所有的事件绑定到结构的最外层,使用一个统一的事件监听器。
这个监听器维持了一个映射,保存所有组件内部的事件监听和处理函数。当事件发生时,首先被这个统一的事件监听器处理,
然后在映射里找到真正的事件处理函数并调用。

合成事件分为以下三个主要过程:

一 事件注册

所有事件都会注册到document上,拥有统一的回调函数dispatchEvent来执行事件分发

二 事件合成

从原生的nativeEvent对象生成合成事件对象,同一种事件类型只能生成一个合成事件Event,如onclick这个类型的事件,dom上所有带有通过jsx绑定的onClick的回调函数都会按顺序(冒泡或者捕获)会放到Event._dispatchListeners 这个数组里,后面依次执行它

三 事件派发

每次触发事件都会执行根节点上 addEventListener 注册的回调,也就是 ReactEventListener.dispatchEvent 方法,事件分发入口函数。该函数的主要业务逻辑如下:
1. 找到事件触发的 DOM 和 React Component
2. 从该 React Component,调用 findParent 方法,遍历得到所有父组件,存在数组中。
3. 从该组件直到最后一个父组件,根据之前事件存储,用 React 事件名 + 组件 key,找到对应绑定回调方法,执行,详细过程为:
4. 根据 DOM 事件构造 React 合成事件。
5. 将合成事件放入队列。
6. 批处理队列中的事件(包含之前未处理完的,先入先处理)

React合成事件的冒泡并不是真的冒泡,而是节点的遍历。

8. React事件处理的特性

React的事件系统和浏览器事件系统相比,主要增加了两个特性:事件代理和事件自动绑定

1、事件代理

1. 区别于浏览器事件处理方式,React并未将事件处理函数与对应的DOM节点直接关联,而是在顶层使用了一个全局事件监听器监听所有的事件;
2. React会在内部维护一个映射表记录事件与组件事件处理函数的对应关系;
3. 当某个事件触发时,React根据这个内部映射表将事件分派给指定的事件处理函数;
4. 当映射表中没有事件处理函数时,React不做任何操作;
5. 当一个组件安装或者卸载时,相应的事件处理函数会自动被添加到事件监听器的内部映射表中或从表中删除。

2、事件自动绑定

1. 在JavaScript中创建回调函数时,一般要将方法绑定到特定的实例,以保证this的正确性;
2. 在React中,每个事件处理回调函数都会自动绑定到组件实例(使用ES6语法创建的例外);

注意:事件的回调函数被绑定在React组件上,而不是原始的元素上,即事件回调函数中的
this所指的是组件实例而不是DOM元素;

3、合成事件

1. 与浏览器事件处理稍微有不同的是,React中的事件处理程序所接收的事件参数是被称为“合成事件(SyntheticEvent)”的实例。
合成事件是对浏览器原生事件跨浏览器的封装,并与浏览器原生事件有着同样的接口,如stopPropagation(),preventDefault()等,并且
这些接口是跨浏览器兼容的。
2. 如果需要使用浏览器原生事件,可以通过合成事件的nativeEvent属性获取

9. React在事件处理的优点

1. 几乎所有的事件代理(delegate)到 document ,达到性能优化的目的
2. 对于每种类型的事件,拥有统一的分发函数 dispatchEvent
3. 事件对象(event)是合成对象(SyntheticEvent),不是原生的
4. react内部事件系统实现可以分为两个阶段: 事件注册、事件分发,几乎所有的事件均委托到document上,而document上事件的回调函数只有一个: ReactEventListener.dispatchEvent,然后进行相关的分发
5. 对于冒泡事件,是在 document 对象的冒泡阶段触发。对于非冒泡事件,例如focus,blur,是在 document 对象的捕获阶段触发,最后在 dispatchEvent 中决定真正回调函数的执行


参考: https://www.jianshu.com/p/99dc37f9edf3
    https://blog.csdn.net/qq_38160012/article/details/80679420
    https://zhuanlan.zhihu.com/p/26742034
    http://www.open-open.com/lib/view/open1488868982317.html
    https://segmentfault.com/a/1190000013364457

猜你喜欢

转载自www.cnblogs.com/mengff/p/9631919.html