一、React事件处理
??????React元素的事件处理和DOM元素的很相似,但是有一点语法上的不同: ①在React中事件的命名采用驼峰命名法,而不是原生dom中的小写,如:onclick要写成onClick,onchange要写成onChange。 ②响应事件的函数要以对象的形式赋值,而不是以dom字符串的形式。
<button onclick="clickMe()">请点击</button>
<button onClick={clickButton}>请点击</button>
**注:**React中的事件是合成事件,并不是dom的原生事件,在dom中可以通过返回false来阻止事件的默认行为,但在react中必须显式的调用事件对象e.preventDefault来阻止事件的默认行为,除了这些点外和原生dom事件并无区别。
二、React中的事件处理函数
??????在React中事件处理函数主要有三种写法,不同的写法解决this指向问题的方式也不同,性能也有差异。
1、使用ES6的箭头函数
(1)在render中使用箭头函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<title>事件处理函数-->在render中使用箭头函数</title>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root')
class MyComponent extends React.Component{
constructor(props){
super(props);
this.state = {
msg:'西安邮电大学'
}
}
render(){
return (
<div>
<button onClick={()=>{console.log(this.state.msg);}}>点击</button>
</div>
)
}
}
ReactDOM.render(<MyComponent/>,root);
</script>
</body>
</html>
??????此时this指向的是当前组件的实例,此方法的优点是不用在构造函数中绑定this,不足是当组件的逻辑比较复杂时,把组件的逻辑写在{}内会导致render函数变得臃肿,不能比较直观的看出ui的结果,代码的可读性也不是很好。
(2)使用class fields语法
??????将箭头函数赋给类的属性,优点:不用在构造函数中绑定this,在render函数中调用简单。
...
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root')
class MyComponent extends React.Component{
constructor(props){
super(props);
this.state = {
msg:'西安邮电大学'
}
}
handClick = ()=> {
console.log(this.state.msg);
}
render(){
return (
<div>
<button onClick={ this.handClick }>点击</button>
</div>
)
}
}
ReactDOM.render(<MyComponent/>,root);
</script>
</body>
...
2、在constructor函数中bind
??????将事件处理函数作为类的成员函数。
...
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root')
class MyComponent extends React.Component{
constructor(props){
super(props);
this.state = {
msg:'西安邮电大学',
number:0
}
this.add = this.add.bind(this)
}
add(){
let num = this.state.number;
num++;
this.setState({
number:num
})
console.log(this.state.number);
}
render(){
return (
<div>
<button onClick={ this.add }>Number++</button>
</div>
)
}
}
ReactDOM.render(<MyComponent/>,root);
</script>
</body>
...
注意: ①在定义事件处理函数时,是无法识别this(即this是undefined的),必须在构造函数中绑定this ②这种方法的好处是每次渲染都不会重新创建一个回调函数,没有额外的性能损失,但是如果在一个组件中有很多的事件函数时,这种在构造函数中绑定this的方法会显得繁琐。
3、在render函数中使用bind
...
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root')
class MyComponent extends React.Component{
constructor(props){
super(props);
this.state = {
msg:'西安邮电大学',
number:0
}
}
add(){
let num = this.state.number;
num++;
this.setState({
number:num
})
console.log(this.state.number);
}
render(){
return (
<div>
<button onClick={ this.add.bind(this) }>Number++</button>
</div>
)
}
}
ReactDOM.render(<MyComponent/>,root);
</script>
</body>
...
注意:此方法在每次render时都会重新创建一个新的函数,性能有一定的损失,但在事件处理函数需要传参时,效果比较好。
4、注意
(1)React事件的命名采用小驼峰式,而不是纯小写,且事件名称后不能加(),否则会直接执行。 (2)不能通过返回false的方式组织默认行为,必须显式调用preventDefalut。 (3)必须谨慎对待JSX回调函数中的this,在JavaScript中,class(类)的方法默认不会绑定this。如果忘记绑定this.textChange并把它传入了onChange,当调用这个函数的时候this的值为undefined。如果觉得bind麻烦,还可以使用箭头函数。
三、事件处理中传参
??????在开发当中,经常遇到对一个列表做操作,可能包含删除、修改、查看。这时绑定事件就需要传参,通常为id。传参的方式有两种。
1、直接传递参数
①在构造函数中给事件处理函数绑定this,调用事件处理函数时直接传参; ??????注:在箭头函数中调用事件处理函数时不需要绑定this。 ②在render函数中调用事件处理函数时进行this的绑定。
...
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件处理中的参数传递</title>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root');
class Clock extends React.Component{
constructor(props){
super(props)
this.state= {
list:[
{
id: 111,
msg: '西安邮电大学'
},
{
id: 222,
msg: '西安理工大学'
},
{
id: 333,
msg: '西安科技大学'
}
],
}
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) } key={item.id}>{ item.msg }</button>
)
}
</div>
)
}
}
ReactDOM.render(<Clock/>,root)
</script>
</body>
</html>
...
2、使用data属性
??????在定义UI控件时使用data自定义属性,在事件处理函数中使用"e.target.dataset.属性名"来获取属性值。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件处理中的参数传递</title>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root');
class Clock extends React.Component{
constructor(props){
super(props)
this.state= {
list:[
{
id: 111,
msg: '西安邮电大学'
},
{
id: 222,
msg: '西安理工大学'
},
{
id: 333,
msg: '西安科技大学'
}
],
msg: '编号:'
}
this.handleClick = this.handleClick.bind(this)
}
handleClick(e){
let str = this.state.msg
console.log(str + e.target.dataset.count)
}
render(){
const { list } = this.state
return (
<div>
{
list.map((item)=>
<button
onClick={ this.handleClick.bind(this) }
key={item.id}
data-count = { item.id}
>{ item.msg }</button>
)
}
</div>
)
}
}
ReactDOM.render(<Clock/>,root)
</script>
</body>
</html>
四、事件流
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件流</title>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root')
const style = {
child: {
width: '100px',
height: '100px',
backgroundColor: 'red'
},
parent: {
width: '150px',
height: '150px',
backgroundColor: 'blue'
},
ancestor: {
width: '200px',
height: '200px',
backgroundColor: 'green'
}
}
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={ ()=>{ console.log('child')}}
style = {style.child }
>
</div>
</div>
</div>
)
}
}
ReactDOM.render(<App/>,root)
</script>
</body>
</html>
??????在该实例中,3个div嵌套显示,并且每个元素上均绑定onClick事件。 ??????当用户点击红色区域时,可以看到控制台先后打印出child->parent->ancestor,这是因为在React的事件处理系统中,默认的事件流就是冒泡 。 ??????注意:如果我们希望以捕获 的方式来触发事件的话,可以使用onClickCapture来绑定事件,也就是在事件类型后面加一个后缀Capture。这样,当点击红色区域div,就可以看到事件流是从ancestor->parent->child传播了。
...
<div
onClickCapture= { ()=>{ console.log('ancestor')}}
style = { style.ancestor }
>
<div
onClickCapture={ ()=>{ console.log('parent')}}
style = { style.parent }
>
<div
onClickCapture={ ()=>{ console.log('child')}}
style = {style.child }
>
</div>
</div>
</div>
...
五、事件委托
??????在合成事件系统中,所有的事件都是绑定在document元素 上,即,虽然我们在某个react元素上绑定了事件,但是,最后事件都委托给document统一触发。 ??????在合成事件中只能组织合成事件中的事件传播。 ??????还是上述例子,我们给红色div的onClick事件,添加stopPropagation() 方法来组织冒泡,此时点击红色div,控制台上只输出了child,说明此时已成功阻止了冒泡。
...
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root')
const style = {
child: {
width: '100px',
height: '100px',
backgroundColor: 'red'
},
parent: {
width: '150px',
height: '150px',
backgroundColor: 'blue'
},
ancestor: {
width: '200px',
height: '200px',
backgroundColor: 'green'
}
}
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')
e.stopPropagation()
}}
style = {style.child }
>
</div>
</div>
</div>
)
}
}
ReactDOM.render(<App/>,root)
</script>
</body>
...
??????可以看到,react阻止的事件流,并没有阻止真正DOM元素的事件触发,当红色div元素被点击时,真正的元素还是按照冒泡的方式,层层将事件交给上级元素进行处理,最后事件传播到document,触发合成事件,在合成事件中,child触发时,e.stopPropagation();被调用,合成事件中的事件被终止。因此,**合成事件中的stopPropagation无法阻止事件在真正元素上的传递,它只会阻止合成事件中的事件流。**相反,如果我们在红色div上绑定一个真正的事件,那么,合成事件将会被终止。
六、事件对象
??????在合成事件(SyntheticEvent)中,依然可以获取到事件发生时的event对象。 ??????我们给div元素绑定了一个click事件,在用户点击时,在控制台输出event对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件对象</title>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root')
const style = {
"mydiv": {
width: '150px',
height: '150px',
backgroundColor: 'red'
}
}
class App extends React.Component{
constructor(props){
super(props)
this.state = {
x: 0,
y: 0
}
}
render(){
return (
<div>
<div style = { style['mydiv']}
onClick = {(e)=>{
console.log(e);
}}
>
X: {this.state.x},Y: { this.state.y }
</div>
</div>
)
}
}
ReactDOM.render(<App />,root)
</script>
</body>
</html>
(1)扩展
??????将用户点击时的坐标在div元素中显示出来,可以通过clientX和clientY来访问。
...
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root')
const style = {
"mydiv": {
width: '150px',
height: '150px',
backgroundColor: 'red'
}
}
class App extends React.Component{
constructor(props){
super(props)
this.state = {
x: 0,
y: 0
}
}
render(){
return (
<div>
<div style = { style['mydiv']}
onClick= { (event)=> {
this.setState({
x: event.clientX,
y: event.clientY
})
console.log(event)
}}
>
X: {this.state.x},Y: { this.state.y }
</div>
</div>
)
}
}
ReactDOM.render(<App />,root)
</script>
</body>
...
??????合成事件中的event对象,并不是原生的event ,只是说,我们可以通过它获取到原生event对象上的某些属性,比如示例中的clientX和clientY。而且,对于这个event对象,在整个合成事件中,只有一个,被全局共享 ,也就是说,当这次事件调用完成之后,这个event对象会被清空 ,等待下一步的事件触发,因此,我们无法在异步的操作中获取到event。
(2)在异步操作中获取event
问题: 当事件触发、响应结束后,event对象会被清空,但是异步数据还没有得到,在得到异步数据之后再去访问event对象的属性就会报错。 解决办法: 先将event对象的某些属性值保存起来,得到异步数据之后再来使用这些属性值。
...
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root')
const style = {
"mydiv": {
width: '150px',
height: '150px',
backgroundColor: 'red'
}
}
class App extends React.Component{
constructor(props){
super(props)
this.state = {
x: 0,
y: 0
}
}
render(){
return (
<div>
<div style = { style['mydiv']}
onClick= { (event)=> {
const { clientX,clientY } = event
setTimeout(()=>{
this.setState({
x: clientX,
y: clientY
})
},1000)
}}
>
X: {this.state.x},Y: { this.state.y }
</div>
</div>
)
}
}
ReactDOM.render(<App />,root)
</script>
</body>
...
(3)原生事件和合成事件的混合使用
**示例:**我们提供一个按钮和一个div元素,当用户点击按钮时,显示div,当点击页面其他区域时,则隐藏。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>原生事件和合成事件的混合使用</title>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
class App extends React.Component{
constructor(props){
super(props)
this.state = {
isShow: 'none'
}
}
button = React.createRef()
componentDidMount(){
document.addEventListener('click',e=>{
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>
)
}
}
ReactDOM.render(<App/>,root)
</script>
</body>
</html>
|