文: 徐超,React 进阶之路作者
React 深入系列 5: 事件处理
React 深入系列, 深入讲解了 React 中的重点概念, 特性和模式等, 旨在帮助大家加深对 React 的理解, 以及在项目中更加灵活地使用 React.
web 应用中, 事件处理是重要的一环, 事件处理将用户的操作行为转换为相应的逻辑执行或界面更新. 在 React 中, 处理事件响应的方式有多种, 本文将详细介绍每一种处理方式的用法, 使用场景和优缺点.
使用匿名函数
先上代码:
- // 代码 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 就无法正常运行了. 所以项目中一般直接使用箭头函数定义的匿名函数作为事件响应.
使用匿名函数的缺点是: 当事件响应逻辑比较复杂时, 匿名函数的代码量会很大, 会导致 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 https://github.com/facebookincubator/create-react-app 创建的应用, 那么这个特性是默认支持的. 你也可以自行在项目中引入 babel 的 https://babeljs.io/docs/plugins/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 进阶之路
来源: https://www.cnblogs.com/ikcamp/p/8989492.html