React事件处理
1 React事件处理
React与DOM事件处理的不同之处:
(1)React事件的命名方式是小驼峰方式,DOM的命名方式是小写。例如:DOM的命名:onclick,React的命名:onClick。
(2)事件处理函数是以对象的方式赋值,而不是以字符串的方式赋值。例如:DOM以字符串方式:onclick = "handleClick()"
,React以对象方式:onClick = { handleClick }
。
(3)阻止默认事件的方式不同。DOM通过返回false来阻止:<a href="www.baidu.com" onclick="javascript:return false;">百度</a>
,React需要显式调用e.preventDefault来阻止。
2 React中事件处理函数
2.1 使用ES6的箭头函数
1、在render函数中使用箭头函数
示例代码:点击按钮,在终端输出0
<div id="root"></div>
<script type="text/babel">
// 获取div
let root = document.getElementById("root");
// 创建类组件
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { // 定义状态机
number: 0
}
}
render() {
return (
<div>
{/* this指向当前组件的实例 */}
<button onClick={(event) => console.log(this.state.number)}>点击</button>
</div>
)
}
}
// 渲染
ReactDOM.render(<MyComponent />, root);
</script>
优点:不用在构造函数中绑定this
缺点:当函数的逻辑比较复杂时,render就显得臃肿,无法直观的看到组件的UI结构,代码可读性差
2、使用class fields语法:将箭头函数赋给类的属性
示例代码:点击按钮在终端输出click
<div id="root"></div>
<script type="text/babel">
// 获取div
let root = document.getElementById("root");
// 创建类组件
class MyComponent extends React.Component {
constructor(props) {
super(props);
}
handleClick = () => {
console.log("click");
}
render() {
return (
<button onClick={this.handleClick}>点击</button>
)
}
}
// 渲染
ReactDOM.render(<MyComponent />, root);
</script>
优点:不用在构造函数中绑定this,在render函数中调用简单
2.2 在constructor函数中bind
在构造函数中进行绑定:将事件处理函数作为类的成员函数
注意:在定义事件处理函数时,是无法识别this(即this是undefined的),必须在构造函数中绑定this
示例代码:点击按钮,在终端输出click
<div id="root"></div>
<script type="text/babel">
// 获取div
let root = document.getElementById("root");
// 创建类组件
class MyComponent extends React.Component {
constructor(props) {
super(props);
// 将事件响应函数绑定到当前组件的实例上
this.handleClick = this.handleClick.bind(this);
}
// 编写事件响应函数
handleClick() {
console.log("click");
}
render() {
return (
// this.handleClick后面不加(),否则代表不点击直接调用
<button onClick={this.handleClick}>点击</button>
)
}
}
// 渲染
ReactDOM.render(<MyComponent />, root);
</script>
优点:在render函数调用时不需要重新创建事件处理函数
缺点:当事件处理很多时,构造函数就显的很繁琐
2.3 在render函数中绑定this
示例代码:点击按钮,在终端输出click
<div id="root"></div>
<script type="text/babel">
// 获取div
let root = document.getElementById("root");
// 创建类组件
class MyComponent extends React.Component {
constructor(props) {
super(props);
}
// 编写事件响应函数
handleClick() {
console.log("click");
}
render() {
return (
<button onClick={this.handleClick.bind(this)}>点击</button>
)
}
}
// 渲染
ReactDOM.render(<MyComponent />, root);
</script>
优点:在调用事件处理函数时,传参比较方便
缺点:每次调用render函数时都重新绑定,导致性能下降
2.4 几种方式的比较
影响 | constroctor函数中bind | 使用class fields语法 | render中使用箭头函数 | 在render中使用bind |
---|---|---|---|---|
render时生成新函数 | 否 | 否 | 是 | 是 |
性能 | 无影响 | 无影响 | 有影响 | 有影响 |
可直接携带参数 | 否 | 否 | 是 | 是 |
简洁性 | 不好 | 好 | 好 | 好 |
3 事件处理中传参
3.1 直接传递参数
1、在构造函数中给事件处理函数绑定this,调用事件处理函数时直接传参
注:在箭头函数中调用事件处理函数时不需要绑定this
示例代码:点击张三按钮,终端输出编号1,点击李四按钮,终端输出编号2
<div id="root"></div>
<script type="text/babel">
// 获取div
let root = document.getElementById("root");
// 创建类组件
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [
{
id: 1,
name: "张三"
},
{
id: 2,
name: "李四"
}
]
}
// 本例中该语句不需要,因为下面的箭头函数中已经绑定了this
// this.handleClick = this.handleClick.bind(this);
}
handleClick(id) {
console.log("编号" + id)
}
render() {
// 获取状态属性值
const { list } = this.state
return (
<div>
{
list.map((item) => <button onClick={() => { this.handleClick(item.id) }}>{item.name}</button>)
}
</div>
)
}
}
// 渲染
ReactDOM.render(<MyComponent />, root);
</script>
2、在render函数中调用事件处理函数时进行this的绑定
示例代码:点击张三按钮,终端输出编号1,点击李四按钮,终端输出编号2
<div id="root"></div>
<script type="text/babel">
// 获取div
let root = document.getElementById("root");
// 创建类组件
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [
{
id: 1,
name: "张三"
},
{
id: 2,
name: "李四"
}
]
}
// 本例中该语句可以没有,因为下面的handleClick()函数中没有使用到this
// this.handleClick = this.handleClick.bind(this);
}
handleClick(id) {
console.log("编号" + id)
}
render() {
// 获取状态属性值
const { list } = this.state
return (
<div>
{
list.map((item) => <button onClick={this.handleClick.bind(this, item.id)}>{item.name}</button>)
}
</div>
)
}
}
// 渲染
ReactDOM.render(<MyComponent />, root);
</script>
3.2 使用data自定义属性
在定义UI控件时使用data自定义属性,在事件处理函数中通过“e.target.dataset.属性名
”来获取UI控件中的data属性值
示例代码:点击张三按钮,终端输出编号1,点击李四按钮,终端输出编号2
<div id="root"></div>
<script type="text/babel">
// 获取div
let root = document.getElementById("root");
// 创建类组件
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [
{
id: 1,
name: "张三"
},
{
id: 2,
name: "李四"
}
]
}
}
handleClick(e) {
// 事件处理函数,e表示触发事件的对象
console.log("编号" + e.target.dataset.count)
}
render() {
// 获取状态属性值
const { list } = this.state
return (
<div>
{
list.map((item) => <button
onClick={this.handleClick.bind(this)}
data-count={item.id} // 标签的自定义属性使用“data-”开头,后面跟着自定义属性名
>{item.name}</button>)
}
</div>
)
}
}
// 渲染
ReactDOM.render(<MyComponent />, root);
</script>
4 事件流
说明:
(1)React的事件流默认是冒泡
(2)React中使用捕获方式:事件类型后面加一个后缀Capture,例如:onClickCapture
示例:冒泡方式传递
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root')
// 1.定义CSS样式
const style = {
child: {
width: '100px',
height: '100px',
backgroundColor: 'red'
},
parent: {
width: '150px',
height: '150px',
backgroundColor: 'blue'
},
ancestor: {
width: '200px',
height: '200px',
backgroundColor: 'green'
}
}
// 2.定义类组件
class App extends React.Component {
render() {
return (
<div onClick={() => { console.log("ancestor") }} style={style.ancestor}>
<div onClick={() => { console.log("parent") }} style={style.parent}>
<div onClick={(e) => {
console.log("child");
}}
style={style.child}>
</div>
</div>
</div>
)
}
}
//3.渲染
ReactDOM.render(<App />, root)
</script>
点击红色区域,按照child、parent、ancestor的顺序在终端输出。
示例:捕获方式传递
// 2.定义类组件
class App extends React.Component {
render() {
return (
<div onClickCapture={() => { console.log("ancestor") }} style={style.ancestor}>
<div onClickCapture={() => { console.log("parent") }} style={style.parent}>
<div onClickCapture={(e) => {
console.log("child");
}}
style={style.child}>
</div>
</div>
</div>
)
}
}
点击红色区域,按照ancestor、parent、child的顺序在终端显示。
5 事件委托
在合成事件系统中,所有事件都是绑定在document元素上,即,虽然我们在某个react元素上绑定了事件,但是,最后事件都委托给document统一触发。在合成事件中只能阻止合成事件中的事件传播。
示例:基于上一部分的案例,阻止事件冒泡
// 2.定义类组件
class App extends React.Component {
render() {
return (
<div onClick={(e) => { console.log("ancestor") }} style={style.ancestor}>
<div onClick={(e) => { console.log("parent") }} style={style.parent}>
<div onClick={(e) => {
console.log("child");
e.stopPropagation(); // 阻止冒泡
}}
style={style.child}>
</div>
</div>
</div>
)
}
}
点击红色区域,只输出child,成功阻止了事件冒泡。
执行流程如下:
可以看到,react 阻止的事件流,并没有阻止真正DOM元素的事件触发,当红色div元素被点击时,真正的元素还是按照冒泡的方式,层层将事件交给上级元素进行处理,最后事件传播到document,触发合成事件,在合成事件中,child触发时,e.stopPropagation();被调用,合成事件中的事件被终止。因此,合成事件中的stopPropagation无法阻止事件在真正元素上的传递,它只阻止合成事件中的事件流。相反,如果我们在红色的div上,绑定一个真正的事件,那么,合成事件则会被终止。
6 事件对象
虽然React事件是合成事件,但是在事件处理中是可以获取事件对象的。
1、在React事件处理中有一个全局事件对象:event对象,每次事件触发后(事件处理函数调用完成后),就会清空event对象,当下一次事件触发时重新获取该对象。该对象不是原生(DOM)的event对象,但是可以通过该对象获取原生对象的部分属性。
示例代码:
<div id="root"></div>
<script type="text/babel">
// 获取div
let root = document.getElementById("root");
// 1.定义样式规则
const style = {
"mydiv": {
width: '150px',
height: '150px',
backgroundColor: 'red'
}
}
// 2.定义类组件
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
x: 0,
y: 0
}
}
render() {
return (
<div>
<div style={style['mydiv']} //以数组的方式设置样式
onClick={(event) => { //'event'对象不是原生的event,但是可以通过event来获取原生的事件对象的部分属性
console.log(event);
}}>
X: {this.state.x},Y: {this.state.y}
</div>
</div>
)
}
}
// 3.渲染
ReactDOM.render(<App />, root)
</script>
示例代码:将用户点击时的坐标在div元素中显示出来
<div id="root"></div>
<script type="text/babel">
// 获取div
let root = document.getElementById("root");
//1.定义样式规则
const style = {
"mydiv": {
width: '150px',
height: '150px',
backgroundColor: 'red'
}
}
//2.定义类组件
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
x: 0,
y: 0
}
}
render() {
return (
<div>
<div style={style["mydiv"]} //以数组的方式设置样式
onClick={(event) => { //'event'对象不是原生的event,但是可以通过event来获取原生的事件对象的部分属性
this.setState({
x: event.clientX,
y: event.clientY
})
}}>
X: {this.state.x},Y: {this.state.y}
</div>
</div>
)
}
}
//3.渲染
ReactDOM.render(<App />, root)
</script>
合成事件中的event对象,并不是原生的event,只是说,我们可以通过它获取到原生event对象_上的某些属性,比如以上示例中的clientX和clientY。而且,对于这个event对象,在整个合成事件中,只有一个被全局共享,也就是说,当这次事件调用完成之后,这个event对象会被清空,等待下一次的事件触发,因此,我们无法在异步的操作中获取到event。
2、在异步操作中获取event。
示例代码:根据上面的示例进行修改
render() {
return (
<div>
<div style={style["mydiv"]}
onClick={(event) => {
setTimeout(() => {
this.setState({
x: event.clientX,
y: event.clientY
})
}, 1000);
}}>
X: {this.state.x},Y: {this.state.y}
</div>
</div>
)
}
}
当用户点击div,一秒后可以看到控制台抛出错误:
问题:当事件触发、响应结束后,event对象会被清空,但是异步数据还没有得到,在得到异步数据之后再去访问event对象的属性就会报错。
解决办法:先将event对象的某些属性值保存起来,得到异步数据之后再来使用这些属性值
示例代码:
render() {
return (
<div>
<div style={style["mydiv"]}
onClick={(event) => {
const { clientX, clientY } = event;
setTimeout(() => {
this.setState({
x: event.clientX,
y: event.clientY
})
}, 1000);
}}>
X: {this.state.x},Y: {this.state.y}
</div>
</div>
)
}
这时点击div,在1秒后就会显示坐标值。
3、原生事件和合成事件的混合使用
React鼓励我们使用合成事件,但是,在某些需求中,还是需要通过原生事件来进行处理,这时就涉及到原生事件和合成事件的混合使用。
示例:用户点击按钮时,显示div,点击页面其他区域隐藏div
<div id="root"></div>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isShow: 'none'
}
}
button = React.createRef() //创建了一个关联属性button
componentDidMount() {
document.addEventListener('click', e => { //原生事件
/*
e.target表示触发click事件的对象
this.button.current表示当前的按钮
*/
if (e.target != this.button.current) {
this.setState({
isShow: 'none'
})
}
})
}
render() {
return (
<div>
<button
ref={this.button}
onClick={() => { // 合成事件
this.setState({
isShow: 'block'
})
}}>点击显示</button>
<br /> <br />
<div style={{
display: this.state.isShow,
width: '100px',
height: '100px',
backgroundColor: 'red'
}}></div>
</div>
)
}
}
//3.渲染
ReactDOM.render(<App />, root)
</script>
点击按钮显示div
点击空白处隐藏div