您的位置:首页 > Web前端 > React

React组件方法中为什么要绑定this

2018-07-20 20:12 691 查看
如果你尝试使用过
React
进行前端开发,一定见过下面这样的代码:

//假想定义一个ToggleButton开关组件
class ToggleButton extends React.Component{
constructor(props){
super(props);
this.state = {isToggleOn: true};
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick(){
this.setState(prevState => ({
isToggleOn: !preveState.isToggleOn
}));
}
handleChange(){
console.log(this.state.isToggleOn);
}
render(){
return(
<button onClick={this.handleClick} onChange={this.handleChange}>
{this.state.isToggleOn ? 'ON':'OFF'}
</button>
)
}
}

构造方法中为什么要给所有的实例方法绑定this呢?

1. 代码执行的细节

上例仅仅是一个组件类的定义,当在其他组件中调用或是使用
ReactDOM.render( )
方法将其渲染到界面上时会生成一个组件的实例,因为组件是可以复用的,面向对象的编程方式非常适合它的定位。根据this指向的基本规则就可以知道,这里的
this
最终会指向组件的实例。

组件实例生成的时候,构造器
constructor
会被执行,此处着重分析一下下面这行代码:

this.handleClick = this.handleClick.bind(this);

此时的
this
指向新生成的实例,那么赋值语句右侧的表达式先查找
this.handleClick( )
这个方法,由对象的属性查找机制(沿原型链由近及远查找)可知此处会查找到原型方法
this.handleClick( )
,接着执行
bind(this)
,此处的
this
指向新生成的实例,所以赋值语句右侧的表达式计算完成后,会生成一个指定了
this
的新方法,接着执行赋值操作,将新生成的函数赋值给实例的
handleClick
属性,由对象的赋值机制可知,此处的
handleClick
会直接作为实例属性生成。总结一下,上面的语句做了一件这样的事情:

把原型方法
handleClick( )
改变为实例方法
handleClick( )
,并且强制指定这个方法中的
this
指向当前的实例。


2. ES5的写法中为什么不用bind(this)?

ES5的写法是指使用
React.createClass( )
方法来定义组件,
React
在V16以上的新版本中已经移除了这个API,你可以通过阅读更早版本的源代码看到这个方法的细节。

//旧版本`react`中`createClass`方法片段
if (this.__reactAutoBindMap) {
this._bindAutoBindMethods();
}

在老版本的
React
中,
createClass()
的定义中可以看到上面的代码,抛开其他复杂的逻辑,从方法名就可以看出这是一个自动绑定的方法,实际上在这个方法中所完成的,就是对组件中自定义方法的
this
强制绑定,感兴趣的读者可以自行翻看源码了解细节。

3. 绑定this的必要性

在组件上绑定事件监听器,是为了响应用户的交互动作,特定的交互动作触发事件时,监听函数中往往都需要操作组件某个状态的值,进而对用户的点击行为提供响应反馈,对开发者来说,这个函数触发的时候,就需要能够拿到这个组件专属的状态合集(例如在上面的开关组件
ToggleButton
例子中,它的内部状态属性
state.isToggleOn
的值就标记了这个按钮应该显示ON或者OFF),所以此处强制绑定监听器函数的
this
指向当前实例的也很容易理解。


React构造方法中的bind会将响应函数与这个组件Component进行绑定以确保在这个处理函数中使用this时可以时刻指向这一组件的实例。


4. 如果不绑定this

如果类定义中没有绑定
this
的指向,当用户的点击动作触发
this.handleClick( )
这个方法时,实际上执行的是原型方法,可这样看起来并没有什么影响,如果当前组件的构造器中初始化了
state
这个属性,那么原型方法执行时,
this.state
会直接获取实例的
state
属性,如果构造其中没有初始化
state
这个属性(比如React中的UI组件),说明组件没有自身状态,此时即使调用原型方法似乎也没什么影响。

事实上的确是这样,这里的
bind(this)
所希望提前规避的,就是著名的this指针丢失的问题


例如使用解构赋值的方式获取某个属性方法时,就会造成引用转换丢失this的问题:

const toggleButton = new ToggleButton();

import {handleClick} = toggleButton;

上例中解构赋值获取到的
handleClick
这个方法在执行时就会报错,Class的内部是强制运行在严格模式下的,此处的
this
在赋值中丢失了原有的指向,在运行时指向了
undefined
,而
undefined
是没有属性的。

另一个存在的限制,是没有绑定
this
的响应函数在异步运行时可能会出问题,当它作为回调函数被传入一个异步执行的方法时,同样会因为丢失了
this
的指向而引发错误。


如果没有强制指定组件实例方法的
this
,在将来的使用中就无法安心使用引用转换作为回调函数传递这样的方式,对于后续使用和协作开发而言都是不方便的。


5. 小结

this
的使用非常灵活,但这种灵活性也带来了很多混乱。这里的bind(this)是为了改进javascript语言级的缺陷,并不是只有React中才需要这样做,这个问题是伴随着面向对象编程而产生的,在使用
javascript
进行插件和框架的开发时,这个问题的影响会更加明显。之所以说它是一个语言级的缺陷,是因为
Java
中对于
this
在同样场景下的指向更符合正常思维逻辑,而
javascript
中如果不显示绑定,就会出现语言运行结果和方法的命名表意不一致的情况。

关于
this
更详尽的分析,可以参考笔者的博文《javascript基础修炼系列-What's this》上下篇(链接地址)。

参考

[1]《ES6-Class基本语法》https://www.cnblogs.com/ChenChunChang/p/8296350.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: