react事件机制

(1)react合成事件

react合成事件执行过程:

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

1. 写法不同,合适事件是驼峰写法,而原生事件是全部小写

2. 执行时机不同,合适事件全部委托到document上,而原生事件绑定到DOM元素本身

3. 合成事件中可以是任何类型,比如this.handleClick这个函数,而原生事件中只能是字符串

// react合成事件

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

// DOM原生事件

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

(2)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父元素合成事件监听器触发

(3)阻止冒泡

基本原则

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

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判断来阻止冒泡事件的执行

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

(4)合成事件执行过程

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合成事件的冒泡并不是真的冒泡,而是节点的遍历。

(5)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属性获取

React在事件处理的优点:

1. 几乎所有的事件代理(delegate)到 document ,达到性能优化的目的

2. 对于每种类型的事件,拥有统一的分发函数 dispatchEvent

3. 事件对象(event)是合成对象(SyntheticEvent),不是原生的

4. react内部事件系统实现可以分为两个阶段: 事件注册、事件分发,几乎所有的事件均委托到document上,而document上事件的回调函数只有一个: ReactEventListener.dispatchEvent,然后进行相关的分发

5. 对于冒泡事件,是在 document 对象的冒泡阶段触发。对于非冒泡事件,例如focus,blur,是在 document 对象的捕获阶段触发,最后在 dispatchEvent 中决定真正回调函数的执行

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容