React 组件中的 bind(this)示例代码


Posted in Javascript onSeptember 16, 2018

React 组件中处理 onClick 类似事件绑定的时候,是需要显式给处理器绑定上下文(context)的,这一度使代码变得冗余和难看。

请看如下的示例:

class App extends Component {
 constructor() {
  super();
  this.state = {
   isChecked: false
  };
 }
 render() {
  return (


   <div className="App">
    <label >
     check me:
     <input
      type="checkbox"
      checked={this.state.isChecked}
      onChange={this.toggleCheck}
     />
    </label>
   </div>
  );
 }

 toggleCheck() {
  this.setState(currentState => {
   return {
    isChecked: !currentState.isChecked
   };
  });
 }
}

页面上放了一个 checkbox 元素,点击之后切换其选中状态。这是很直观的一段代码,但并不会像你想的那样正常工作。

React 组件中的 bind(this)示例代码

事件处理器上下文丢失的报错

因为 checkbox 的 onChange 事件处理器中,找不到 React 组件的 setState 方法,这说明其执行时的上下文不是该组件,而是别的什么东西,具体我们来调试下。

React 组件中的 bind(this)示例代码

调试查看丢失上下文后 this 的值

出乎意料,是 undefined,这个方法在一个完全野生的环境下执行,没有任何上下文。

WHY

当然这并不是 React 的锅,这是 JavaScript 中 this 的工作原理。具体可参见 Chapter 2: this All Makes Sense Now! 来追溯其底层原因,简单来讲 this 的值取决于函数调用的方式。

默认的绑定

function display(){
 console.log(this)
}

display() // 严格模式下为全局 `window`,非严格模式下为 `undefined`

隐式绑定

通过对象来调用,该函数的上下文被隐式地指定为该对象。

var obj = {
 name: 'Nobody',
 display: function(){
  console.log(this.name);
  }
 };
 obj.display(); // Nobody. 里面取的是 obj 身上的 `name` 属性

但,如果把该对象上的方法赋值给其他变量,或通过参数传递的形式,再执行,那光景就又不一样了。

var obj = {
 name: "Nobody",
 display: function() {
  console.log(this.name);
 }
};
var name = "global!";
var outerDisplay = obj.display;
outerDisplay(); // global! 这里取到的 `name` 是全局中的内个

这里赋值给 outerDisplay 后再调用,等同于调用一个普通函数,而不是对象中的那个,所以此时 this 为全局对象,刚好全局里面有定义一个 name 变量。同样地,如果是严格模式下,因为此时 this 为 undefined,所以访问不到所谓的 undefiend.name,于是会抛错。

function invoker(fn) {
 fn();
}

setTimeout( obj.display, 1000 ); // global!
invoker(obj.display); // global!

这里 setTimeout 调用的时候,因为它的签名实际上是 setTimeout(fn,delay),所以,可以理解为将 obj.display 赋值给了它的入参 fn,实际上执行的是 fn 而不再是对象上的方法了。对于 invoker 函数也是一样的道理。

强制绑定

这个时候,bind 就成了那个拯救世界的英雄,任何时间我们都可以通过它来显式地指定函数的执行上下文。

var name = “global!”;
obj.display = obj.display.bind(obj); 
var outerDisplay = obj.display;
outerDisplay(); // Nobody

bind 将指定的上下文与函数绑定后返回一个新的函数,这个新函数再拿去赋值或传参什么的都不会对其上下文产生影响了,执行时始终是我们指定的那个。

现场还原

有了上面的背景,就可以还原文章开头的问题了,即事件处理器的上下文 丢失的问题。

JSX 中的 HTML 标签本质上对应 React 中创建该标签的一个函数。比如你写的 div 编译会其实是 React.createElement(‘div')。所以当你书写 <Input> 时其实是调用了 React.createElement 来创建一个 <Input> 标签。

React.createElement(
 type,
 [props],
 [...children]
)

标签上的属性会作为 props 参数传递给 createElement 函数。

<Input onChange={this.toggleCheck}> 表示将组件中的 toggleCheck 方法赋值给 createElement 的入参 props(props 是个对象,接收所有书写在标签上的属性,),实际调用的时候一如上面所说的,调用的已经不是组件中的 toggleCheck 方法了。

React.createElement(type, props){
 // 让我们创建一个 <type> 并在 <type> 的值发生变化的时候调用一下 `props.onChange`
 ...
 props.onChange() // 它已经不是原来的方法了,丢失了上下文
 ...
}

因为 ES6 的 Class 是在严格模式下执行的,所以事件处理器中如果使用了 this 那它就是 undefined。

所以你看到 React 官方的示例中,constructor 里有 bind(this) 的语句就不奇怪了,就是为了纠正这个事件处理器歪了的执行上下文。

constructor() {
  super();
  this.state = {
   isChecked: false
  };
+ this.toggleCheck = this.toggleCheck.bind(this);
 }

这样是能正常工作了,但是,这句代码的存在真的很别扭,因为,

•对于业务来说,毫无意义,徒增代码量
•很丑陋,每加一个处理器就要加一条这样的绑定
•冗余,这样重复的代码大量冗余在项目中,在搜索中混淆了原本的方法

避免的方式有很多,就看哪种最对味。下面来看看如何避免写这些绑定方法。

#0行内的绑定

最简单的可以在行内进行绑定操作,这样不用单独写一句出来。

<input
      type="checkbox"
      checked={this.state.isChecked}
-       onChange={this.toggleCheck}
+      onChange={this.toggleCheck.bind(this)}
   />

#1箭头函数

因为箭头函数不会创建新的作用域,其上下文是语义上的(lexically)上下文。所以在绑定事件处理器时,直接使用剪头函数是很方便的一种规避方法。

<input
      type="checkbox"
      checked={this.state.isChecked}
-       onChange={this.toggleCheck}
+      onChange={() => this.toggleCheck()}
     />

#2将类的方法改成属性

如果将这个处理器作为该组件的一个属性,这个属性作为事件的处理器以箭头函数的形式存在,执行的时候也是能正常获取到上下文的。

- toggleCheck() {
+ toggleCheck = () => {
  this.setState(currentState => {
   return {
    isChecked: !currentState.isChecked
   };
  });
 }

总结

React 组件中,其实跟 React 没多大关系,传递事件处理器,或方法作为回调时,其上下文会丢失。为了修复,我们需要显式地给这个方法绑定一下上下文。除了常用的在构造器中进行外,还可通过箭头函数,公有属性等方式来避免冗余的绑定语句。

Javascript 相关文章推荐
9个javascript语法高亮插件 推荐
Jul 18 Javascript
再谈JavaScript线程
Jul 10 Javascript
javascript稀疏数组(sparse array)和密集数组用法分析
Dec 28 Javascript
js实现网页定位导航功能
Mar 07 Javascript
Vue实现virtual-dom的原理简析
Jul 10 Javascript
AngularJS 控制器 controller的详解
Oct 17 Javascript
Vue Cli 3项目使用融云IM实现聊天功能的方法
Apr 19 Javascript
js中的this的指向问题详解
Aug 29 Javascript
在JavaScript中实现链式调用的实现
Dec 24 Javascript
Vue 中 template 有且只能一个 root的原因解析(源码分析)
Apr 11 Javascript
2020淘宝618理想生活列车自动领喵币js脚本的代码
Jun 02 Javascript
vue radio单选框,获取当前项(每一项)的value值操作
Sep 10 Javascript
Vue.set()动态的新增与修改数据,触发视图更新的方法
Sep 15 #Javascript
解决VUE中document.body.scrollTop为0的问题
Sep 15 #Javascript
Vue实现调节窗口大小时触发事件动态调节更新组件尺寸的方法
Sep 15 #Javascript
vue 监听某个div垂直滚动条下拉到底部的方法
Sep 15 #Javascript
vue--点击当前增加class,其他删除class的方法
Sep 15 #Javascript
vue-cli 使用vue-bus来全局控制的实例讲解
Sep 15 #Javascript
在vue中实现点击选择框阻止弹出层消失的方法
Sep 15 #Javascript
You might like
php表单转换textarea换行符的方法
2010/09/10 PHP
PHP下通过file_get_contents的代理使用方法
2011/02/16 PHP
MySQL时间字段究竟使用INT还是DateTime的说明
2012/02/27 PHP
PHP性能测试工具xhprof安装与使用方法详解
2018/04/29 PHP
javascript 隐藏/显示指定的区域附HTML元素【legend】用法
2010/03/05 Javascript
仿中关村在线首页弹出式广告插件(jQuery版)
2012/05/03 Javascript
setTimeout函数兼容各主流浏览器运行执行效果实例
2013/06/13 Javascript
jQuery将所有被选中的checkbox某个属性值连接成字符串的方法
2015/01/24 Javascript
jQuery选择器之基本选择器与层次选择器
2015/03/03 Javascript
AngularJS转换响应内容
2016/01/27 Javascript
关于微信中a链接无法跳转问题
2016/08/02 Javascript
分享一道关于闭包、bind和this的面试题
2017/02/20 Javascript
jQuery实现分页功能(含ajax请求、后台数据、附完整demo)
2017/04/03 jQuery
React Native中导航组件react-navigation跨tab路由处理详解
2017/10/31 Javascript
详解webpack提取第三方库的正确姿势
2017/12/22 Javascript
vue绑定的点击事件阻止冒泡的实例
2018/02/08 Javascript
Bootstrap模态对话框中显示动态内容的方法
2018/08/10 Javascript
JavaScript实现PC端横向轮播图
2020/02/07 Javascript
[01:07:13]TNC vs Pain 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/20 DOTA
[02:02]特效爆炸!DOTA2珍宝之瓶待你开启
2018/08/21 DOTA
用Python进行一些简单的自然语言处理的教程
2015/03/31 Python
解析Python中while true的使用
2015/10/13 Python
python中实现精确的浮点数运算详解
2017/11/02 Python
python实现简单登陆流程的方法
2018/04/22 Python
python实现梯度下降算法
2020/03/24 Python
Python计算库numpy进行方差/标准方差/样本标准方差/协方差的计算
2018/12/28 Python
python实现自动化上线脚本的示例
2019/07/01 Python
Python实现生成密码字典的方法示例
2019/09/02 Python
JBL澳大利亚官方商店:扬声器、耳机和音响系统
2018/05/24 全球购物
美国女士内衣在线折扣商店:One Hanes Place
2019/03/24 全球购物
物业管理个人自我评价
2013/11/08 职场文书
国税会议欢迎词
2014/01/16 职场文书
环保倡议书400字
2014/05/15 职场文书
部队反四风对照检查材料
2014/09/26 职场文书
预备党员转正党小组意见
2015/06/01 职场文书
详解Laravel框架的依赖注入功能
2021/05/27 PHP