一文解锁 React 中绑定事件处理函数的新姿势!

2018 年 6 月 9 日 CSDN

点击上方“CSDN”,选择“置顶公众号”

关键时刻,第一时间送达!


React中的事件处理函数绑定看似容易,但还是需要一些技巧。如果你了解Perl和Python的历史,就应该知道TMTOWTDI和TOOWTDI这两个词的意思:Perl的理念是TMTOWTDI,即There's More Than One Way To Do It(完成一项任务有很多种方法),而Python的理念是TOOWTDI,即There's Only One Way To Do It(完成一项任务只有一种方法)。在历史上,Perl曾以这种多样性闻名,但实践证明,过多的实现方法只会给代码造成混乱,从软件工程的角度来看,它远远不如Python奉行的单一方法理念。很不幸,JavaScript就是个TMTOWTDI语言,所以混乱也是在所难免了。

这篇文章将探讨在React中绑定事件处理函数的常见方法,以及每种方法的优缺点。本文最重要的目的就是帮你找到那个“唯一的方法”。

我假设你明白函数绑定的原因和必要性,比如至少应该理解 this.handler.bind(this) 中的 .bind(this) 的含义,理解 function() { console.log(this); } 和 () => { console.log(this); } 的区别。如果对于这两个问题有疑惑,可以先搜索下关于函数绑定的知识。


在render()函数中动态绑定


第一种常用的方法就是在 .render() 函数中直接调用 .bind(this) 实现绑定。例如:

class HelloWorld extends Component {
 handleClick(event) {}
 render() {
   return (
     <p>Hello, {this.state.name}!</p>
     <button onClick={this.handleClick.bind(this)}>Click</button>
   );
 }
}

当然从功能上来看这样做完全没问题。但考虑一下这样一个问题:如果 this.state.name 发生改变会怎样?

答案似乎很显然:this.state.name 改变会导致整个组件重绘。没错,组件会重绘name部分。但button会不会重绘?

我们知道React使用Virtual DOM,每当绘制发生时,React会比较更新后的Virtual DOM和上一次的Virtual DOM,然后在画面上仅更新DOM树中改变了的部分。

在这个例子中,当 render() 被调用时,this.handleClick.bind(this) 也会被调用。注意:该调用会生成一个全新的绑定好的事件处理函数,这个函数与第一次 render() 被调用时的事件处理函数是不同的!

动态绑定情况下的Virtual DOM,蓝色的元素将被重绘

如上图所示,第一次 render() 被调用时,this.handleClick.bind(this) 返回 funcA,因此React认为 onChange 是 funcA。

之后当 render() 再次被调用时,this.handleClick.bind(this) 返回 funcB(注意它每次被调用都会返回一个新的函数)。于是,React认为 onChange 从 funcA 变成了 funcB,意味着button也会被重绘。

有人说就一个按钮其实无所谓了,但如果是100个按钮呢?

render() {
 return (
   {this.state.buttons.map(btn => (
     <button key={btn.id} onChange={this.handleClick.bind(this)}>
       {btn.label}
     </button>

   ))}
 );
}

在上例中,任何 label 的改变都将导致所有的按钮被重绘(理想状况是只有label变更了的那个按钮才重绘,其他按钮不会),因为这个循环会为每个按钮生成新的 onChange 处理函数。


在constructor()中绑定


传统的做法是在构造函数中进行绑定。非常朴实的做法:

class HelloWorld extends Component {
 constructor() {
   this.handleClick = this.handleClickFunc.bind(this);
 }
 render() {
   return (<button onClick={this.handleClick}/>);
 }
}

该方法要比第一种方法好得多。调用 render() 不会为 onClick 生成新的事件处理函数,因此只要按钮不发生变化, <button> 就不会被重绘。

在构造函数中进行绑定情况下的Virtual DOM,蓝色的元素将被重绘


利用箭头函数绑定


通过ES7的类属性功能(现在可以通过Babel实现:https://babeljs.io/docs/plugins/transform-class-properties/),可以在定义方法的时候进行绑定:

class HelloWorld extends Component {
 handleClick = (event) => {
   console.log(this.state.name);
 }
 render() {
   return (<button onClick={this.handleClick}/>)
 }
}

上述代码中,handleClick 被赋值为绑定后的函数,这种写法相当于:

constructor() {
 this.handleClick = (event) => { ... };
}

因此,在组件初始化结束之后,this.handleClick 的值就不会再变化。因此,这样做能保证 <button> 不会被重绘。个人认为这是实现绑定的最好的办法。代码写起来很简单、易懂,而且能达到我们的目的。


利用箭头函数为多个元素实现动态绑定


利用同样的技巧,可以用箭头函数给多个输入绑定同一个处理函数:

class HelloWorld extends Component {
 handleChange = name => event => {
   this.setState({ [name]: event.target.value });
 }
 render() {
   return (
     <input onChange={this.handleChange('name')}/>
     <input onChange={this.handleChange('description')}/>
   )
 }
}

乍一看貌似没问题,而且实现极其简单明了。但仔细想想,就会发现这种方法与第一种方法有同样的问题:每次 render() 被调用时,两个 <input> 都会被重绘。

不过个人认为这种方法其实问题不大,毕竟实现简单,而且我不愿意为每个输入框都写一个 handleXXXChange 函数。而且最重要的是,这种“多用处理函数”几乎不太可能出现在一个列表中。也就是说,只会有固定数量的<input>元素被重绘(而不像列表那样会产生任意多个),一般不会造成性能问题。

毕竟,这种写法带来的便利性要远远大于性能损失,所以我还是推荐这种做法的。


利用autobind自动绑定


还有一种方法是利用 lodash-decorators 或 react-autobind 等包,在定义类时自动绑定所有方法。我个人不太喜欢这种自动化处理,而是倾向于把绑定限制在必需的最小范围,但自动绑定的确能在保证代码可读性的同时节省很多工作量。代码大概类似于:

import autoBind from 'react-autobind';
class HelloWorld() {
 constructor() {
   autoBind(this);
 }
 handleClick() {
   ...
 }
 render() {
   return (<button onClick={this.handleClick}/>);
 }
}



登录查看更多
0

相关内容

React.js(React)是 Facebook 推出的一个用来构建用户界面的 JavaScript 库。

Facebook开源了React,这是该公司用于构建反应式图形界面的JavaScript库,已经应用于构建Instagram网站及 Facebook部分网站。最近出现了AngularJS、MeteorJS 和Polymer中实现的Model-Driven Views等框架,React也顺应了这种趋势。React基于在数据模型之上声明式指定用户界面的理念,用户界面会自动与底层数据保持同步。与前面提及 的框架不同,出于灵活性考虑,React使用JavaScript来构建用户界面,没有选择HTML。Not Rest

机器学习速查手册,135页pdf
专知会员服务
340+阅读 · 2020年3月15日
自动结构变分推理,Automatic structured variational inference
专知会员服务
39+阅读 · 2020年2月10日
Transformer文本分类代码
专知会员服务
116+阅读 · 2020年2月3日
【电子书】C++ Primer Plus 第6版,附PDF
专知会员服务
87+阅读 · 2019年11月25日
机器学习相关资源(框架、库、软件)大列表
专知会员服务
39+阅读 · 2019年10月9日
用 Python 开发 Excel 宏脚本的神器
私募工场
26+阅读 · 2019年9月8日
Python用于NLP :处理文本和PDF文件
Python程序员
4+阅读 · 2019年3月27日
抖音爬虫
专知
3+阅读 · 2019年2月11日
React Native 分包哪家强?看这文就够了!
程序人生
13+阅读 · 2019年1月16日
Python3.8新特性概览
Python程序员
4+阅读 · 2018年12月8日
Python3.7中一种懒加载的方式
Python程序员
3+阅读 · 2018年4月27日
干货 | Python 爬虫的工具列表大全
机器学习算法与Python学习
10+阅读 · 2018年4月13日
用Python制作3D动画
Python程序员
30+阅读 · 2018年1月17日
如何轻松解锁神经网络的数学姿势
ImportNew
6+阅读 · 2018年1月4日
python pandas 数据处理
Python技术博文
4+阅读 · 2017年8月30日
3D Deep Learning on Medical Images: A Review
Arxiv
12+阅读 · 2020年4月1日
Arxiv
6+阅读 · 2018年11月1日
Feature Selection Library (MATLAB Toolbox)
Arxiv
7+阅读 · 2018年8月6日
Arxiv
20+阅读 · 2018年1月17日
VIP会员
相关VIP内容
相关资讯
用 Python 开发 Excel 宏脚本的神器
私募工场
26+阅读 · 2019年9月8日
Python用于NLP :处理文本和PDF文件
Python程序员
4+阅读 · 2019年3月27日
抖音爬虫
专知
3+阅读 · 2019年2月11日
React Native 分包哪家强?看这文就够了!
程序人生
13+阅读 · 2019年1月16日
Python3.8新特性概览
Python程序员
4+阅读 · 2018年12月8日
Python3.7中一种懒加载的方式
Python程序员
3+阅读 · 2018年4月27日
干货 | Python 爬虫的工具列表大全
机器学习算法与Python学习
10+阅读 · 2018年4月13日
用Python制作3D动画
Python程序员
30+阅读 · 2018年1月17日
如何轻松解锁神经网络的数学姿势
ImportNew
6+阅读 · 2018年1月4日
python pandas 数据处理
Python技术博文
4+阅读 · 2017年8月30日
Top
微信扫码咨询专知VIP会员