基本使用React框架自身实现了一套事件处理机制,它的基本用法和DOM事件很相似。例如,给某个react元素绑定点击事件:事件类型采用小驼峰命名法,因此是 onClick,而不是 onclick,其他事件类型相同。直接将函数的声明当成事件句柄传递我们将它的这套事件处理机制称之为SyntheticEvent,即合成事件Synthetic Event支持的事件类型React支持的事件类型如下:Clipboard EventsComposition EventsKeyboard EventsFocus EventsForm EventsMouse EventsPointer EventsSelection EventsTouch EventsUI EventsWheel EventsMedia EventsImage EventsAnimation EventsTransition EventsOther Events事件流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”));在该示例中,3个div嵌套显示,并且每个元素上均绑定onClick事件:当用户点击红色区域的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 传播了。事件委托在合成事件系统中,所有的事件都是绑定在document元素上,即,虽然我们在某个react元素上绑定了事件,但是,最后事件都委托给document统一触发。在合成事件中只能阻止合成事件中的事件传播我们在红色区域的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 阻止的事件流,并没有阻止真正DOM元素的事件触发,当红色div元素被点击时,真正的元素还是按照冒泡的方式,层层将事件交给上级元素进行处理,最后事件传播到docuement,触发合成事件,在合成事件中,child触发时,e.stopPropagation();被调用,合成事件中的事件被终止。因此,合成事件中的stopPropagation无法阻止事件在真正元素上的传递,它只阻止合成事件中的事件流。相反,如果我们在红色的div上,绑定一个真正的事件,那么,合成事件则会被终止。事件对象在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对象:接下来将用户点击时的坐标在div元素中显示出来,可以通过clientX和clientY来访问:<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对象上的某些属性,比如以上示例中的clientX和clientY。而且,对于这个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>当用户点击div,1秒之后获取点击时的坐标,这时,可以看到控制台抛出错误:在异步操作中想要获取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的功能:要实现 点击其他区域隐藏div元素 的功能,需要将事件绑定在document元素上,接下来,在compnentDidMount生命周期函数中,来绑定该事件:class App extends Component { state = { … } componentDidMount() { document.addEventListener(“click”, e => { this.setState({ isShow: “none” }); }) } render() { … }}当点击按钮时,isShow: “block”,当点击其他区域时,isShow: “none”。这时我们发现,点击按钮时,div显示不出来了。这个现象的原因是,实际上,在document元素身上,现在已经存在2个click事件,一个是合成事件绑定的click,另外一个是我们自己添加的监听器。当用户点击按钮时,synthetic中的click首先被触发,这时,isShow状态被设置成block,页面元素被显示出来,紧跟着,native中的click事件被触发,又把isShow的状态改为none,div元素又被隐藏了起来。处理方法: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”));