由一个小例子由浅入深地分析,如下图所示,想要实现点击h1标签中的文字实现切换效果(即修改react状态来驱动页面渲染)
目录
你需要的关于类的一丢丢的基础
<script>
class Demo1 {
change() {
console.log(this)
}
}
const d1 = new Demo1;
d1.change();
const x = d1.change;
x();
class Demo2 {
change = function () {
console.log(this)
}
}
const d2 = new Demo2;
d2.change();
const y = d2.change;
y();
</script>
上述代码中的四个console输出如上图所示;
- 首先执行d1.change(),新构建的实例d1中没有change方法,于是顺着原型链找到demo1的原型对象中的change方法,调用后打印this为Demo1 {}(即this指向新构建的实例d1)
- 然后执行const x = d1.change; x();这两句是把Demo1原型中的方法change传给x,然后在调用x(),但是为什么打印this为undifined呢?因为类中的方法自动开启了严格模式,说人话就是当类中的方法在全局(window)调用的时候,指向this指向不再是window,而是undifined
- 再看到Demo2,这里就有意思了,在类中直接写了一个赋值语句,这是什么鬼?这种写法相当于给实例添加change方法.注意,change方法被添加进了实例d2中,不信你看第三行输出d1自带change方法然而第一行输出的d1就是个空的
- 最后一行输出,竟然是undifined,为啥?不是给d2身上添加了change方法了吗?没错,d2身上是由change方法了,但是执行const y = d2.change; y();这相当于在全局调用d2上的方法change,参考第二条的严格模式,所以打印undifined,这便是万恶之源
重头戏,react中的this指向丢失问题
由于注释全写在代码中影响观看,故用索引
1. state1(this丢失):
<script type="text/babel">
class Weather extends React.Component {
constructor(props){
super(props)
this.state = {isHot:true,isOut:false};
}
render(){
//@1
return (<h1 onClick={this.changeWeather}>
今天天气{this.state.isHot ? '炎热' : '凉爽'},
我要{this.state.isOut ? '外出' : '呆在家'}
</h1>)
}
changeWeather(){
//@2
console.log(this);
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
- 给h1标签绑定onclick,回调函数指向Weather原型对象中的方法changeWeather
当点击事件触发来到这儿的时候,react执行回调函数changeWeather,但是这儿打印的this是undifined,为啥?原来类中的方法在全局执行的时候(这里是把Weather类中的方法changeWeather交给react作为点击事件的回调函数时), 方法内的this是undifined,因为es6中所有方法自动开启了严格模式(即this不让指向window,直接指向undifined(如果看不懂可以参考class基础中的第二行输出,简单来说就是在全局中执行了changeWeather)
打印结果上图所示
2. state2(解决办法1)
<script type="text/babel">
class Weather extends React.Component {
constructor(props){
super(props)
this.state = {isHot:true,isOut:false};
//@3
this.changeWeather = this.changeWeather.bind(this);
}
render(){
//@1
//@4
return (<h1 onClick={this.changeWeather}>
今天天气{this.state.isHot ? '炎热' : '凉爽'},
我要{this.state.isOut ? '外出' : '呆在家'}
</h1>)
}
changeWeather(){
//@2
//@5
console.log(this.state);
this.setState({isHot:!this.state.isHot});
this.setState({isOut:!this.state.isOut});
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
给h1标签绑定onclick,回调函数指向Weather原型对象中的方法changeWeather
当点击事件触发来到这儿的时候,react执行回调函数changeWeather,但是这儿打印的this是undifined. 原因:类中的方法在全局执行的时候(这里是把Weather类中的方法changeWeather交给react作为点击事件的回调函数时),方法内的this是undifined,因为es6中所有方法自动开启了严格模式(即this不让指向window,直接指向undifined)
重头戏来了!!!(bind是个好东西),以下是个骚操作
需要知道的:constructor中的this指向Weather实例,changeWeather这个方法存在于Weather的原型中. 解决办法1:可以通过以下代码解决.这是一个赋值语句,看等号右边,首先通过this.changeWeather顺着原型找到changeWeather,然后调用bind方法 (注:demo.bind(this)相当于this.demo(),会返回一个拥有新的this指向的函数) 所以在这里调用bind(this),这个this在constructor构造器中指向实例.所以this.changeWeather.bind(this)返回了一个this指向Weather构造的实例的changeWeather函数;接着看等号左边,this.changeWeather即给实例一个属性changeWeather,而这个属性等于之前所说的等号右边返回的新函数(重点是新函数的this指向了实例) 总结:原理就是给实例追加一个属性,是一个函数,且这个函数的this指向实例
当调用onclick回调函数的时候,该回调函数是this.chanWeather即该实例上自带的属性,且this指向自身,大功告成!我们不就是想找回那些年我们失去的this吗
这时候打印this,指向的不再是undifined,而是Weather实例
其实第二个demo我们已经完美地解决了this的指向问题,但是!不够骚,不够装杯,接下来深入一层
3. state3(第二种解决办法及问题)
<script type="text/babel">
class Weather extends React.Component {
//@1
state = {isHot:true,isOut:false};
changeWeather = function(){
//@2
console.log(this);
}
render(){
//@3
return (<h1 onClick={this.changeWeather}>
今天天气{this.state.isHot ? '炎热' : '凉爽'},
我要{this.state.isOut ? '外出' : '呆在家'}
</h1>)
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
在类中用a = 100 这种赋值语句的时候,相当于给类的实例追加a = 100这个属性,以下的state和changeWeather同理(这儿看不懂的可以参考class基础中的demo2)
这里的打印的结果是undifiend,因为就算是实例拥有了changeWeather这个方法,也必须通过this.changeWeather这种形式去调用,这样方法中的this才会指向实例.但是(接3.)
实例中的方法changeWeather作为onclick回调函数传给react(注意,this.changeWeather是函数,this.changeWeather()才是调用)
3. state4(终究方案)
<script type="text/babel">
class Weather extends React.Component {
state = {isHot:true,isOut:false};
//@1
changeWeather = () => {
console.log(this.state);
this.setState({isHot:!this.state.isHot});
this.setState({isOut:!this.state.isOut});
}
render(){
return (<h1 onClick={this.changeWeather}>
今天天气{this.state.isHot ? '炎热' : '凉爽'},
我要{this.state.isOut ? '外出' : '呆在家'}
</h1>)
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
解决state3中问题的方法,利用箭头函数(需要知道的:箭头函数没有this,在里面使用this会向外寻找this指向).
描述一下过程:
- react创建一个Weather实例(这个实例一创建便有了state状态属性和changeWeather方法,天赋拉满),我们假设这个实例是w
- 然后react调用w.render()返回一个h1标签,但是这个标签带有onclick事件,这个事件的回调函数是w身上的changeWeather.,而这个方法是个箭头函数,当事件触发的时候调用箭头函数,由于箭头函数没有this,向外寻找,找到了实例x,故箭头函数中的this指向实例x
- 接下来执行changeWeather中的内容,this没丢失,如鱼得水,顺利地修改了实例的状态,补充一句:修改状态得调用x.setState(),而不能直接写x.state.isHot
修改完实例状态,接下来便是渲染了,简简单单