React 中的事件处理

基本使用

React框架自身实现了一套事件处理机制,它的基本用法和DOM事件很相似。例如,给某个react元素绑定点击事件:

《React 中的事件处理》

  • 事件类型采用小驼峰命名法,因此是 onClick,而不是 onclick,其他事件类型相同。
  • 直接将函数的声明当成事件句柄传递

我们将它的这套事件处理机制称之为SyntheticEvent,即合成事件

Synthetic Event

支持的事件类型

React支持的事件类型如下:

事件流

React中,默认的事件传播方式为冒泡

import React, { Component } from "react"
import ReactDOM from "react-dom"

const styles = {
    child: {
        width: "100px",
        height: "100px",
        backgroundColor: "red"
    },
    parent: {
        width: "150px",
        height: "150px",
        backgroundColor: "blue"
    },
    ancestor: {
        width: "200px",
        height: "200px",
        backgroundColor: "black"
    }
}

class App extends Component {
    render() {
        return (
            <div
                onClick={() => {
                    console.log("ancestor")
                }}
                style={styles.ancestor}>
                <div
                    onClick={() => {
                        console.log("parent");
                    }}
                    style={styles.parent}>
                    <div
                        onClick={() => {
                            console.log("child");
                        }}
                        style={styles.child}>
                    </div>
                </div>
            </div>
        );
    }
}

ReactDOM.render(<App />, document.querySelector("#root"));

在该示例中,3div嵌套显示,并且每个元素上均绑定onClick事件:

《React 中的事件处理》

当用户点击红色区域的div元素时,可以看到,控制台先后输出了child -> parent -> ancestor,这是因为在React的事件处理系统中,默认的事件流就是冒泡,如果说我们希望以捕获的方式来触发事件的话,可以使用onClickCapture来绑定事件,也就是在事件类型后面加一个后缀Capture

import React, { Component } from "react"
import ReactDOM from "react-dom"

const styles = {
    child: {
        width: "100px",
        height: "100px",
        backgroundColor: "red"
    },
    parent: {
        width: "150px",
        height: "150px",
        backgroundColor: "blue"
    },
    ancestor: {
        width: "200px",
        height: "200px",
        backgroundColor: "black"
    }
}

class App extends Component {
    render() {
        return (
            <div
                onClickCapture={() => {
                    console.log("ancestor")
                }}
                style={styles.ancestor}>
                <div
                    onClickCapture={() => {
                        console.log("parent");
                    }}
                    style={styles.parent}>
                    <div
                        onClickCapture={() => {
                            console.log("child");
                        }}
                        style={styles.child}></div>
                </div>
            </div>
        );
    }
}

ReactDOM.render(<App />, document.querySelector("#root"));

这时,还是点击红色区域的div,就可以看到事件流是从ancestor -> parent -> child 传播了。

《React 中的事件处理》

事件委托

在合成事件系统中,所有的事件都是绑定在document元素上,即,虽然我们在某个react元素上绑定了事件,但是,最后事件都委托给document统一触发。

《React 中的事件处理》

在合成事件中只能阻止合成事件中的事件传播

我们在红色区域的div里,也就是最里层的那个元素上,使用e.stopPropagation()方法来阻止事件流的传播:

<div
    onClick={() => {
        console.log("ancestor")
    }}
    style={styles.ancestor}>
    <div
        onClick={() => {
            console.log("parent");
        }}
        style={styles.parent}>
        <div
            onClick={e => {
                console.log("child");
                e.stopPropagation();
            }}
            style={styles.child}></div>
    </div>
</div>

点击红色区域的,我们可以看到控制台上只输出了child,说明这时已经成功阻止了冒泡。执行流程如下:

《React 中的事件处理》

从图中我们可以看到,react 阻止的事件流,并没有阻止真正DOM元素的事件触发,当红色div元素被点击时,真正的元素还是按照冒泡的方式,层层将事件交给上级元素进行处理,最后事件传播到docuement,触发合成事件,在合成事件中,child触发时,e.stopPropagation();被调用,合成事件中的事件被终止。因此,合成事件中的stopPropagation无法阻止事件在真正元素上的传递,它只阻止合成事件中的事件流。相反,如果我们在红色的div上,绑定一个真正的事件,那么,合成事件则会被终止。

《React 中的事件处理》

事件对象

SyntheticEvent中,我们依然可以获取到事件发生时的event对象:

import React, { Component } from "react"
import ReactDOM from "react-dom"

const styles = {
  "DEBUG_DISPLAY": {
    width: "150px",
    height: "150px",
    backgroundColor: "red"
  }
}

class App extends Component {

  state = {
    x: 0,
    y: 0
  }

  render() {
    return (
      <div>
        <div
          style={styles["DEBUG_DISPLAY"]}
          onClick={e => {
            console.log(e)
          }}
          >
          x: {this.state.x},y: {this.state.y}
        </div>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.querySelector("#root"));

以上示例中,我们给div元素绑定了一个click事件,在用户点击时,在控制台输出event对象:

《React 中的事件处理》

接下来将用户点击时的坐标在div元素中显示出来,可以通过clientXclientY来访问:

<div
    style={styles["DEBUG_DISPLAY"]}
    onClick={e => {
        this.setState({
            x: e.clientX,
            y: e.clientY
        });
    }}
    >
    x: {this.state.x},y: {this.state.y}
</div>

合成事件中的event对象,并不是原生的event,只是说,我们可以通过它获取到原生event对象上的某些属性,比如以上示例中的clientXclientY。而且,对于这个event对象,在整个合成事件中,只有一个,被全局共享,也就是说,当这次事件调用完成之后,这个event对象会被清空,等待下一次的事件触发,因此,我们无法在异步的操作中获取到event

<div
    style={styles["DEBUG_DISPLAY"]}
    onClick={e => {
        setTimeout(() => {
            this.setState({
                x: e.clientX,
                y: e.clientY
            });
        }, 1000);
    }}
    >
    x: {this.state.x},y: {this.state.y}
</div>

当用户点击div1秒之后获取点击时的坐标,这时,可以看到控制台抛出错误:

《React 中的事件处理》

在异步操作中想要获取event对象中的数据,在事件发生时就需要将数据通过变量保存下来:

<div
    style={styles["DEBUG_DISPLAY"]}
    onClick={e => {
        const { clientX, clientY } = e;
        setTimeout(() => {
            this.setState({
                x: clientX,
                y: clientY
            });
        }, 1000);
    }}
    >
    x: {this.state.x},y: {this.state.y}
</div>

混合使用

react鼓励我们使用合成事件,但是,在某些需求中,还是需要通过原生事件来进行处理,这时,就涉及到合成事件和原生事件的混合使用,例如以下示例:

import React, { Component } from "react"
import ReactDOM from "react-dom"

class App extends Component {

  state = {
    isShow: "none"
  }

  render() {
    return (
      <div>
        <button
          onClick={() => {
            this.setState({
              isShow: "block"
            });
          }}
        >点击显示</button>
        <div style={{
          display: this.state.isShow,
          width: "100px",
          height: "100px",
          backgroundColor: "red"
        }}></div>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.querySelector("#root"));

在这个示例中,我们提供一个按钮和一个div元素,当用户点击按钮时,显示div,当点击页面其他区域时,则隐藏。以上代码已经实现点击按钮显示div的功能:

《React 中的事件处理》

要实现 点击其他区域隐藏div元素 的功能,需要将事件绑定在document元素上,接下来,在compnentDidMount生命周期函数中,来绑定该事件:

class App extends Component {

  state = {
    ...
  }

  componentDidMount() {
    document.addEventListener("click", e => {
        this.setState({
            isShow: "none"
        });
    })
  }

  render() {
    ...
  }
}

当点击按钮时,isShow: "block",当点击其他区域时,isShow: "none"。这时我们发现,点击按钮时,div显示不出来了。

《React 中的事件处理》

这个现象的原因是,实际上,在document元素身上,现在已经存在2click事件,一个是合成事件绑定的click,另外一个是我们自己添加的监听器。当用户点击按钮时,synthetic中的click首先被触发,这时,isShow状态被设置成block,页面元素被显示出来,紧跟着,native中的click事件被触发,又把isShow的状态改为nonediv元素又被隐藏了起来。

处理方法:

import React, { Component } from "react"
import ReactDOM from "react-dom"

class App extends Component {

  state = {
    isShow: "none"
  }

  button = React.createRef();

  componentDidMount() {
    document.addEventListener("click", e => {
      // 当 native 事件被触发时,我们判断一下当前目标元素是否为 button,
      // 如果不是点击的按钮,则就意味着将元素隐藏
      if (e.target !== this.button.current) {
        this.setState({
          isShow: "none"
        });
      }
    })
  }

  render() {
    return (
      <div>
        <button
          ref={this.button}
          onClick={() => {
            this.setState({
              isShow: "block"
            });
          }}
        >点击显示</button>
        <div style={{
          display: this.state.isShow,
          width: "100px",
          height: "100px",
          backgroundColor: "red"
        }}></div>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.querySelector("#root"));
    原文作者:佛也要生活
    原文地址: https://segmentfault.com/a/1190000017855734
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞