最近在使用React+Typescript重构一个应用,后面看到同事在写react组件的方法时,是采用箭头函数的写法。这让我想起在 React Class Component 绑定事件时,经常会通过 bind(this) 来绑定事件,比如:
class Fn extends React.Component{
constructor( props ){
super( props );
this.handleClick = this.handleClick.bind(this);
}
// 1. 普通函数的写法
handleClick(event){
// todo something
}
// 2. 箭头函数的写法
handleArrowClick = (event) => {
}
render(){
return (
<div>
<button type="button" onClick={this.handleClick}>
Click Me
</button>
<button type="button" onClick={this.handleArrowClick}>
Click Me
</button>
</div>
);
}
}
为什么我们需要bind(this)在组件中?为什么可以用箭头函数代替bind(this)在组件?
JavaScript 中 this 绑定机制
默认绑定(Default Binding)
function display(){
console.log(this); // this 指向全局对象
}
display();
display( )在全局的 window 作用域调用,所以函数内的 this 默认指向全局的 window, 在 strict 模式 this 的值为undefined。
隐式绑定(Implicit binding)
var obj = {
name: 'coco',
display: function(){
console.log(this.name); // this 指向 obj
}
};
obj.display(); // coco
当我们通过obj调用 display( )时,this 上下文指向 obj, 但是当我们将display( ) 赋给一个变量,比如:
var name = "oh! global";
var outerDisplay = obj.display;
outerDisplay(); // oh! global
display 被赋给 outerDisplay 这个变量,调用 outerDisplay( ) 时,相当于Default Binding,this 上下文指向 global, 因此 this.name 找到的是全局的 name。 很多时候,我们需要将函数作为参数通过callback方式来调用,也会使这个函数失去它的 this 上下文,比如:
function handleClick(callback) {
callback()
}
var name = 'oh! global';
handleClick(obj.display);
// oh! global
当调用handleClick方法时,JavaScript重新将 obj.display 赋予 callback 这个参数,相当于 callback = obj.display ,display这个函数在handleClick作用域环境,就像Default Binding,里面的 this 指向全局。
显式绑定(Explicit binding)
为了避免上面问题,我们可以通过 bind( ) 来显式绑定 this 的值。
var name = "oh! global";
obj.display = obj.display.bind(obj);
var outerDisplay = obj.display;
outerDisplay();
// coco
真正的原因在 JavaScript 不在 React
回到开始我们的问题:为什么 React 组件事件绑定需要 bind 来绑定,如果我们不绑定,this 的值为 undefined。
class Foo {
constructor(name){
this.name = name
}
display(){
console.log(this.name);
}
}
var foo = new Foo('coco');
foo.display(); // coco
// 下面例子类似于在 React Component 中 handle 方法当作为回调函数传参
var display = foo.display;
display() // TypeError: this is undefined
我们在实际 React 组件例子中,假设 handleClick 方法没有通过 bind 绑定,this 的值为 undefined, 它和上面例子类似handleClick 也是作为回调函数传参形式。 但是我们代码不是在 strict 模式下, 为什么 this 的值不是全局对象,就像前面的 default binding,而是undefined? 因为 class 类不管是原型方法还是静态方法定义,“this”值在被调用的函数内部将为 undefined,具体原因见详细。
同样,我们为了避免这个问题需要 bind 绑定:
class Foo {
constructor(name){
this.name = name
this.display = this.display.bind(this);
}
display(){
console.log(this.name);
}
}
var foo = new Foo('coco');
foo.display(); // coco
var display = foo.display;
display(); // coco
当然,我们可以不在 constructor 中绑定 this, 比如:
var foo = new Foo('coco');
foo.display = foo.display.bind(foo);
var display = foo.display;
display(); // coco
但是,在 constructor 中绑定是最佳和最高效的地方,因为我们在初始化 class 时已经将函数绑定,让 this 指向正确的上下文。
不用bind 绑定方式
当然,实际写 React Class Component 还有其他的一些方式来使 this 指向这个 class , 那么为什么需要别的方式,不使用bind绑定方式呢?
-
这种写法难看不说,还会对 React 组件的 shouldComponentUpdate 优化造成影响。
-
这是因为 React 提供了 shouldComponentUpdate 让开发者能够控制避免不必要的 render,还提供了在 shouldComponentUpdate 自动进行 Shallow Compare 的 React.PureComponent, 继承自 PureComponent 的组件只要 props 和 state 中的值不变,组件就不会重新 render。
-
然而如果用了 bind this,每次父组件渲染,传给子组件的 props.onClick 都会变,PureComponent 的 Shallow Compare 基本上就失效了,除非你手动实现 shouldComponentUpdate.
最常用的 public class fields
这是因为我们使用 public class fields 语法,handleClick 箭头函数会自动将 this 绑定在 Foo 这个class, 具体就不做探究。
箭头函数
class Fn extends React.Component{
handleClick = (event) => {
console.log(this);
}
// 相当于
constructor(props){
super(props)
this.handleClick = (event) => {
console.log(this);
}
}
render(){
return (
<button type="button" onClick={(e) => this.handleClick(e)}>
Click Me
</button>
// or
<button type="button" onClick= {this.handleClick}>
Click Me
</button>
);
}
}
这是因为在ES6中,箭头函数 this 默认指向函数的宿主对象(或者函数所绑定的对象)。
其他
还有一些方法来使 this 指向 Foo 上下文,比如通过 ::绑定等,具体就不展开,可以自己去查 React 绑定 this 的几种方式。
总结
-
React class 组件中,事件的 handle 方法其实就相当于回调函数传参方式赋值给了 callback,在执行 click 事件时 类似 element.addEventListener('click', callback, false ), handle 失去了隐式绑定的上下文,this 的值为 undefined。(为什么是 undefined 而不是 global,上文有解释)。
-
所以我们需要在 初始化调用 constructor 就通过 bind() 绑定 this, 当然我们不用 bind( )方式来绑定也可以有其他一些方法来时 this 指向正确的上下文。
参考: