为什么不能这样写
假设我现在要通过点击事件执行一个函数,如:
constructor(){
super()
this.state={n:0}
}
add(){this.setState({n:n+1})}
上面的add函数执行了add方法,add方法内执行setState方法并且设置了一个新的state对象为{n:n+1},下面我写点击事件的执行函数
<button onClick={this.add}></button> //这样写成this.add行吗?
如果是vue的话,这样的写法当然没有问题,vue通过双向数据绑定让页面渲染与数据互相影响,当然Vue应该写成@click="add",我们对此不用深究写法而更注重于两者间的编程思想。
如果是react,通过编译后,上面的代码会变成这样的代码
button.onclick.call(null,event)=this.add
//相当于
button.onclick.call(null,event)=()=>{this.setState(...)}//这里的this是null
通过点击后,会显性得将this.add函数内部的this改成null(this.add的this依然是react对象的this),但内部的this.setState中的this变成了null,如果一个函数的this是null,就会变成window,所以写成onClick={this.add}是不对的
两种正确写法
我们通过控制this来对上面的写法进行修改
<button onClick={()=>{this.add()}}></button> //第一种写法
<button onClick={this.add.bind(this)}></button> //第二种写法
第一种写法的好处在于,由于箭头函数中的this取决于定义时上下文的特点所以里面的this.add内部的this并不是null,而是会被解析成react对象的this,那么add方法内部的this自然也会变成react对象的this,这种方法很好地控制了this的流向
第二种方法直接将this显性绑定为react对象,自然也没有问题
改造
那么如果我觉得上面两种写法都很麻烦,可以对其进行改造吗?
我还是想写成this.add
答案是可以的,我们让它变成
constructor(){
super()
this.state={n:0}
this.add=()=>{this.setState({n:n+1})}//最终写法,变成对象的实例方法
}
或者
constructor(){
super()
this.state={n:0}
}
add=()=>{this.setState({n:n+1})} //最终写法,变成挂在对象上的方法
//add(){this.setState(...)}
// 最开始是这样写的,这种写法会变成实例对象的原型方法 注意=和:的区别
两者的写法是等价的
他们都是将add函数写在constructor里,变成实例对象自身的属性和方法
那么,你可能会问,为什么写在constructor里,就不会引发this指向变成window的问题呢?
原因是,constructor的this是构造函数的this,它用来帮助我们把这个函数绑定在react实例对象上了,这里的this并不是指运行环境
原理
我们用一个简易的例子搞懂这里面的this指向
let reactObj={
n:0,
add(){console.log(this.n)}
}
reactObj.add() // 输出多少?
window.n=1000
let button={
click:reactObj.add
}
button.click.call(null)//输出多少?
上面的代码中,分别输出0、1000。
执行函数时,this的指向会根据环境的不同而改变,在执行button.clicl.call(null)时,this.n变成了null.n==>window.n,于是就产生了上面的答案,说明这里的this是会变化的,它记录的是当前的运行环境。
那么我改良成一个构造函数
class React{
constructor(){
this.n=0,
this.add=()=>{console.log(this.n)}
}
}
let reactObj=new React()
reactObj.add() // 0
window.n=1000
let button={
click:reactObj.add
}
button.click.call(null) // 0
由于构造函数中的constructor里面this并不是代表运行环境的this了,它指向实例对象,此时的reactObj变成了什么呢?我们比较一下
//原来的
reactObj={
n:0,
add(){console.log(this.n)} //运行环境this
}
//后来的
reactObj={
n:0,
add(){console.log(reactObj.n)} //this变成了实例对象
}
再返回去看我们最初的疑问
那么,你可能会问,为什么写在constructor里,就不会引发this指向变成window的问题呢?
结论就是写在constructor里的this不是我们认识的this了,它只是构造实例对象属性的关键字而已,代表的是实例对象。
而我们不写在constructor内的this它就代表运行环境,它当然会根据运行环境的改变而改变。
回顾一下
我们来回顾一下事件绑定函数的写法:
constructor(){
super()
this.state={n:0}
this.add=()=>{
this.setState({n:n+1})
}
constructor(){
super()
this.state={n:0}
}
add=()=>{
this.setState({n:n+1})
}//这样写就相当于this.add=()=>{}
现在可以写成这样了
<button onClick={this.add}></button>
最后的建议
由于this相对比较复杂,而react为了减轻学习成本,创造了函数组件,如果你希望摆脱this,那么请尽量使用函数组件(实际上函数组件还更方便)