文:徐超,《React进阶之路》做者前端
受权发布,转载请注明做者及出处react
###React 深刻系列5:事件处理git
React 深刻系列,深刻讲解了React中的重点概念、特性和模式等,旨在帮助你们加深对React的理解,以及在项目中更加灵活地使用React。github
Web应用中,事件处理是重要的一环,事件处理将用户的操做行为转换为相应的逻辑执行或界面更新。在React中,处理事件响应的方式有多种,本文将详细介绍每一种处理方式的用法、使用场景和优缺点。bash
先上代码:babel
//代码1
class MyComponent extends React.Component {
render() {
return (
<button onClick={()=>{console.log('button clicked');}}>
Click
</button>
);
}
}
复制代码
点击Button的事件响应函数是一个匿名函数,这应该是最多见的处理事件响应的方式了。这种方式的好处是,简单直接。哪里须要处理事件响应,就在哪里定义一个匿名函数处理。代码1中的匿名函数使用的是箭头函数,咱们也能够不使用箭头函数:前端工程师
//代码2
class MyComponent extends React.Component {
render() {
return (
<button onClick={function(){console.log('button clicked');}}>
Click
</button>
);
}
}
复制代码
虽然代码2的运行效果和代码1相同,但实际项目中不多见到代码2的这种写法。这是由于箭头函数解决了this绑定的问题,能够将函数体内的this绑定到当前对象,而不是运行时调用函数的对象。若是响应函数中须要使用this.state,那么代码2就没法正常运行了。因此项目中通常直接使用箭头函数定义的匿名函数做为事件响应。app
使用匿名函数的缺点是:当事件响应逻辑比较复杂时,匿名函数的代码量会很大,会致使render函数变得臃肿,不容易直观地看出组件最终渲染出的元素结构。另外,每次render方法调用时,都会从新建立一个匿名函数对象,带来额外的性能开销,当组件的层级越低时,这种开销就越大,由于任何一个上层组件的变化均可能会触发这个组件的render方法。固然,在大多数状况下,这点性能损失是能够没必要在乎的。函数
代码以下:性能
//代码3
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {number: 0};
this.handleClick = this.handleClick.bind(this); // 手动绑定this
}
handleClick() {
this.setState({
number: ++this.state.number
});
}
render() {
return (
<div>
<div>{this.state.number}</div>
<button onClick={this.handleClick}>
Click
</button>
</div>
);
}
}
复制代码
点击Button的事件响应函数是组件的方法:handleClick。这种方式的好处是:每次render方法的调用,不会从新建立一个新的事件响应函数,没有额外的性能损失。可是,使用这种方式要在构造函数中为做为事件响应的方法(handleClick),手动绑定this: this.handleClick = this.handleClick.bind(this)
,这是由于ES6 语法的缘故,ES6 Class 的方法默认不会把this绑定到当前的实例对象上,须要咱们手动绑定。每次都手动绑定this是否是有点繁琐?好吧,让咱们来看下一种方式。
使用ES7的属性初始化语法( property initializers ),代码能够这样写:
//代码4
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {number: 0};
}
handleClick = () => {
this.setState({
number: ++this.state.number
});
}
render() {
return (
<div>
<div>{this.state.number}</div>
<button onClick={this.handleClick}>
Click
</button>
</div>
);
}
}
复制代码
这样一来,不再用手动绑定this了。可是你须要知道,这个特性还处于试验阶段,默认是不支持的。若是你是使用官方脚手架Create React App 建立的应用,那么这个特性是默认支持的。你也能够自行在项目中引入babel的transform-class-properties插件获取这个特性支持。
事件响应函数默认是会被传入一个事件对象Event做为参数的。若是想传入其余参数给响应函数应该怎么办呢?
使用第一种方式的话很简单,直接使用新参数:
//代码5
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [1,2,3,4],
current: 1
};
}
handleClick(item,event) {
this.setState({
current: item
});
}
render() {
return (
<ul>
{this.state.list.map(
(item)=>(
<li className={this.state.current === item ? 'current':''}
onClick={(event) => this.handleClick(item, event)}>{item}
</li>
)
)}
</ul>
);
}
}
复制代码
onClick的响应函数中,方法体内能够直接使用新的参数item。
使用第二种方式的话,能够把绑定this的操做延迟到render中,在绑定this的同时,绑定额外的参数:
//代码6
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [1,2,3,4],
current: 1
};
}
handleClick(item) {
this.setState({
current: item
});
}
render() {
return (
<ul>
{this.state.list.map(
(item)=>(
<li className={this.state.current === item ? 'current':''}
onClick={this.handleClick.bind(this, item)}>{item}
</li>
)
)}
</ul>
);
}
}
复制代码
使用第三种方式,解决方案和第二种基本一致:
//代码7
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [1,2,3,4],
current: 1
};
}
handleClick = (item) => {
this.setState({
current: item
});
}
render() {
return (
<ul>
{this.state.list.map(
(item)=>(
<li className={this.state.current === item ? 'current':''}
onClick={this.handleClick.bind(undefined, item)}>{item}
</li>
)
)}
</ul>
);
}
}
复制代码
不过这种方式就有点鸡肋了,由于虽然你不须要经过bind函数绑定this,但仍然要使用bind函数来绑定其余参数。
关于事件响应函数,还有一个地方须要注意。无论你在响应函数中有没有显式的声明事件参数Event,React都会把事件Event做为参数传递给响应函数,且参数Event的位置老是在其余自定义参数的后面。例如,在代码6和代码7中,handleClick
的参数中虽然没有声明Event参数,但你依然能够经过arguments[1]
获取到事件Event对象。
总结一下,三种事件处理的方式,第一种有额外的性能损失;第二种须要手动绑定this,代码量增多;第三种用到了ES7的特性,目前并不是默认支持,须要Babel插件的支持,可是写法最为简洁,也不须要手动绑定this。通常推荐使用第二种和第三种方式。
新书推荐《React进阶之路》
做者:徐超
毕业于浙江大学,硕士,资深前端工程师,长期就任于能源物联网公司远景智能。8年软件开发经验,熟悉大前端技术,拥有丰富的Web前端和移动端开发经验,尤为对React技术栈和移动Hybrid开发技术有深刻的理解和实践经验。
2019年,iKcamp原创新书《Koa与Node.js开发实战》已在京东、天猫、亚马逊、当当开售啦!