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">
let root = document.getElementById("root");
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
}
}
render() {
return (
<div>
{}
<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">
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">
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 (
<button onClick={this.handleClick}>点击</button>
)
}
}
ReactDOM.render(<MyComponent />, root);
</script>
优点:在render函数调用时不需要重新创建事件处理函数 缺点:当事件处理很多时,构造函数就显的很繁琐
2.3 在render函数中绑定this
示例代码:点击按钮,在终端输出click
<div id="root"></div>
<script type="text/babel">
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">
let root = document.getElementById("root");
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [
{
id: 1,
name: "张三"
},
{
id: 2,
name: "李四"
}
]
}
}
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">
let root = document.getElementById("root");
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [
{
id: 1,
name: "张三"
},
{
id: 2,
name: "李四"
}
]
}
}
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">
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) {
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}
>{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')
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");
}}
style={style.child}>
</div>
</div>
</div>
)
}
}
ReactDOM.render(<App />, root)
</script>
点击红色区域,按照child、parent、ancestor的顺序在终端输出。 示例:捕获方式传递
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统一触发。在合成事件中只能阻止合成事件中的事件传播。 示例:基于上一部分的案例,阻止事件冒泡
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">
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) => {
console.log(event);
}}>
X: {this.state.x},Y: {this.state.y}
</div>
</div>
)
}
}
ReactDOM.render(<App />, root)
</script>
示例代码:将用户点击时的坐标在div元素中显示出来
<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
})
}}>
X: {this.state.x},Y: {this.state.y}
</div>
</div>
)
}
}
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()
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>
点击按钮显示div 点击空白处隐藏div
|