react-class-this

46 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第33天,点击查看活动详情

react之一大难点----this----攻克它

所在场景:

    //点击按钮,改变事件。
    class Clock extends React.Component{
        constructor(props){
            super(props);
            this.state = { time: new Date().toLocaleTimeString }
        }
    }
    render(){
        return (
            <div><h1>time:{this.state.time}</h1></div>
           <button>按钮</button>
        )
    }
复制代码

逐一升级方法。

  1. 方法1:
 go(){ this.setState({ time: new Date().toLocaleTimeString })}
 <button onClick={this.go}>go</button>
复制代码

写好go方法,测试运行一下查看结果。失败。

找原因:打印this,查看this指向。

 go(){ 
 console.log(this) //undefined
 this.setState({ time: new Date().toLocaleTimeString })
 }
 <button onClick={this.go}>go1</button>
复制代码

打印后可知,结果为undefined。为什么呢?

原因:this.go在点击时去执行,本身this.go的含义就是在this中去寻找go方法然后执行this.setState()进而改变time的值。

image.png

之前说到class中的this指向的为实例,而this.go语句并不是去调用go方法,而是在实例中寻找到go方法,进而执行this.setState()。在找到go方法后打印this之所以为undefined,是因为没有调用go的主人,所以this.setState中的this为undefined,所以,更改time失败。

  1. 方法2:
render(){
        return (
            <div><h1>time:{this.state.time}</h1></div>
           <button>按钮</button>
        )
    }
 go(){ 
 console.log(this) 
 this.setState({ time: new Date().toLocaleTimeString })
 }
 <button onClick={this.go()}>go2</button>
复制代码

this.go()并非上一个例子一样,该语句为直接执行go函数,在this(实例)上绑定了go()。所以,初始实例加载完就会去执行go函数,无需点击,就会自动执行。

此时的this指向谁呢?

答:实例Clock.所以time会成功改变。

但还有一个问题,每次改变完time后都会进入render重新渲染,渲染完又自动执行go函数,长此以往会溢出。

image.png

所以,该方法也失败。

  1. 方法3:
 <button onClick={() => {
//组件实例中的setState,所以好使。this指向clock
this.setState({ time: new Date().toLocaleTimeString })
}}>go3</button>
复制代码

再次提到这个隐藏概念:render中this全部指向类组件实例。所以在组件实例中成功调用setState方法去改变state中的time。所以,成功。

但,每次点击都会冲i性能渲染,性能不好。且在jsx标签中写js语法看起来也过于臃肿复杂,如何简化呢?

  1. 方法4:
 <button onClick={() => {
//绑定了函数,箭头函数里的this是指向render的。
this.go()
}}>go4</button>
复制代码

已知,render中this全部指向类组件实例,所以在实例中调用go(),成功使go方法中的this指向了实例而非第一种方法的undefined。所以该方法也成功调用了改变time的方法,但还是之前的问题,虽然写法看起来简化了,可也是在箭头函数里调用函数,已知函数是特殊的对象,占用的是堆内存空间,那么和方法3一样每次都要开辟新的内存空间,导致占用内存,性能不好。所以要避免调用函数的情况,优化内存。

  1. 方法5:

回看方法1,是想要通过this.go就调用go方法,却因为go方法中的this指向undefined而没有成功调用setState()。从这一角度出发,是不是只要将go方法中的this指向实例就可以成功执行setState方法了呢,且没有开辟新内存空间的情况。

于是思考,将this改变为实例。用到的方法为apply,call,bind三者任意即可。

<button onClick={this.go.bind(this, 'aaa')}>go5</button>
复制代码

以上,成功将this指向实例Clock。

已知bind方法改变this指向时原理是 新生成一个指向实例的函数,去代替给原来指向undefined函数。

存在问题:每点击一次,都要重新绑定函数。

所以继续思考,是不是可以把绑定方法绑定在constructor构造函数中。

  1. 方法6:

测试点击按钮时可以发现:

image.png

render是当state改变时就重新渲染的,而constructor只执行一次。所以将this一次性绑定在constuctor。

class Clock extends React.Component {
            constructor(props) {
                console.log("constructor");
                super(props);
                //通过this挂载将state到实例上
                this.state = { time: new Date().toLocaleTimeString() }
                this.go = this.go.bind(this) //this.go = 新的函数(改变后的)
}    
 <button onClick={this.go}>go6</button>

复制代码
  1. 方法7:

在箭头函数内部的this指向为实例,所以可以从源头上把go函数写成箭头函数,这样直接调用就可以避免以上需要改变this指向的问题。

//ES7最新写法
go = () => {
console.log(this)//Clock
this.setState({ time: new Date().toLocaleTimeString() })
}
<button onClick={this.go}>go7</button>
复制代码