最近在使用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 (
Click Me
Click Me
);
}
}
为什么我们需要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 (
Click Me
// or
Click Me
);
}
}
这是因为在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 指向正确的上下文。
参考:
为什么在React Component需要bind绑定事件
函数作为React组件的方法时, 箭头函数和普通函数的区别是什么?
友情链接:
Copyright © 2022 世界杯金靴_足球小子世界杯 - ffajyj.com All Rights Reserved.