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 相关文章推荐
WEB高性能开发之疯狂的HTML压缩
Jun 19 Javascript
js+css实现的简单易用兼容好的分页
Dec 30 Javascript
javascript数组操作(创建、元素删除、数组的拷贝)
Apr 07 Javascript
js设置控件的隐藏与显示的两种方法
Aug 21 Javascript
javascript实现dom元素可拖动
Mar 21 Javascript
js基于myFocus实现轮播图效果
Feb 14 Javascript
基于vue的fullpage.js单页滚动插件
Mar 20 Javascript
angularjs实现分页和搜索功能
Jan 03 Javascript
解决vue 更改计算属性后select选中值不更改的问题
Mar 02 Javascript
4 种滚动吸顶实现方式的比较
Apr 09 Javascript
jsonp实现百度下拉框功能的方法分析
May 10 Javascript
基于vue-cli搭建多模块且各模块独立打包的项目
Jun 12 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数组函数知识汇总
2016/05/12 PHP
javascript优先加载笔记代码
2008/09/30 Javascript
js/jquery获取文本框输入焦点的方法
2014/03/04 Javascript
jquery实现的随机多彩tag标签随机颜色和字号大小效果
2014/03/27 Javascript
javascript自定义函数参数传递为字符串格式
2014/07/29 Javascript
简介可以自动完成UI的AngularJS工具angular-smarty
2015/06/23 Javascript
jquery实现漂亮的二级下拉菜单代码
2015/08/26 Javascript
通过Tabs方法基于easyUI+bootstrap制作工作站
2016/03/28 Javascript
JS组件系列之Bootstrap table表格组件神器【终结篇】
2016/05/10 Javascript
基于javascript实现表格的简单操作
2016/05/21 Javascript
js格式化时间的简单实例
2016/11/27 Javascript
nodejs搭建本地服务器并访问文件的方法
2017/03/03 NodeJs
详解vue 模拟后台数据(加载本地json文件)调试
2017/08/25 Javascript
Vue filter介绍及其使用详解
2017/10/21 Javascript
Vue基于NUXT的SSR详解
2017/10/24 Javascript
jquery使用FormData实现异步上传文件
2018/10/25 jQuery
ES6中异步对象Promise用法详解
2019/07/31 Javascript
转换layUI的数据表格中的日期格式方法
2019/09/19 Javascript
vue+element 实现商城主题开发的示例代码
2020/03/26 Javascript
vue从后台渲染文章列表以及根据id跳转文章详情详解
2020/12/14 Vue.js
基于Python_脚本CGI、特点、应用、开发环境(详解)
2017/05/23 Python
快速了解Python相对导入
2018/01/12 Python
Python3删除排序数组中重复项的方法分析
2019/01/31 Python
Django框架中序列化和反序列化的例子
2019/08/06 Python
使用Python将字符串转换为格式化的日期时间字符串
2019/09/01 Python
Python3.x+迅雷x 自动下载高分电影的实现方法
2020/01/12 Python
Python Numpy 控制台完全输出ndarray的实现
2020/02/19 Python
Python 字典一个键对应多个值的方法
2020/09/29 Python
纯css3实现效果超级炫的checkbox复选框和radio单选框
2014/09/01 HTML / CSS
three.js模拟实现太阳系行星体系功能
2019/09/03 HTML / CSS
两只小狮子教学反思
2014/02/05 职场文书
夫妻房产协议书的格式
2014/10/11 职场文书
刑事辩护授权委托书范本
2014/10/17 职场文书
赢在执行观后感
2015/06/16 职场文书
JavaScript实现简单拖拽效果
2021/09/15 Javascript
redis的list数据类型相关命令介绍及使用
2022/01/18 Redis