react報錯 TypeError: Cannot read property ‘setState’ of undefined

程式碼如下:

class test extends Component {
    constructor(props) {
        super(props);
        this.state = {
            liked: false
        };
    }
    handleClick(event) {
        this.setState({liked: !this.state.liked});
    }
    render() {
        var text = this.state.liked ? '喜歡' : '不喜歡';
        return (
            <div onClick={this.handleClick}><b>{text}</b>我。點我切換狀態。
            </div>
        );
    }
 
}
export default test;

可以正常展示頁面:

但是按鈕一按就會報錯。

為什麼會出現這種情況呢?

因為點擊按鈕時,到了handleClick()方法中的this已經不是組件里的this了。

第一種解決方法是:手動綁定this。將

constructor(props) {
    super(props);
    this.state = {
        liked: false
    };
}

改為

constructor(props) {
    super(props);
    this.state = {
        liked: false
    };
    this.handleClick = this.handleClick.bind(this);//手動綁定
}

第二種解決辦法是:將

handleClick(event) {
        this.setState({liked: !this.state.liked});
}

改為

handleClick= (e) => {
        this.setState({liked: !this.state.liked});
}

這種解決方法之所以能解決問題,就引申到了另外一個問題:函數作為React組件的方法時, 箭頭函數和普通函數的區別是什麼?

舉個例子:下面2個a的定義有什麼區別?

class App extends Component {
  a() {
    console.log(1)
  }
 
  a = () => {
    console.log(1)
  }
}

第一個 a 不必說,是原型方法的定義。寬鬆模式下對應 ES5 就是

App.prototype.a = function() {}
第二個是 Stage 2 Public Class Fields 裡面的寫法,babel 下需要用 Class properties transform Plugin 進行轉義。相當於:
class App extends Component {
  constructor (...args) {
    super(...args)
    this.a = () => {
        console.log(1)
    }
  }
}

 

為什麼需要第二種寫法?

在 React 裡面,要將類的原型方法通過 props 傳給子組件,傳統寫法需要 bind(this),否則方法執行時 this 會找不到:

<button onClick={this.handleClick.bind(this)}></button>

或者

<button onClick={(e) => this.handleClick(e)}></button>

這種寫法難看不說,還會對 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 的這種寫法,就解決了這個問題。另外還有其他若干種辦法,比如先定義原型方法,然後在 constructor 裡面 bind 一遍;或者使用 decorator 進行 bind 等:

class A {
  constructor() {
    this.a = this.a.bind(this)
  }
 
  a() {}
 
  // or
  @bindthis
  b() {}
}

而箭頭函數除了程式碼少。與普通函數最大的不同就是:this是由聲明該函數時候定義的,一般是隱性定義為聲明該函數時的作用域this。

var a = ()=>{
    console.log(this)
}
//等同於
var a = function(){
    console.log(this)
}.bind(this);
 
a(); //Window
var b = function(){
    console.log(this)
};
b(); //Window
var obj = { a,b };
obj.a(); //Window
obj.b(); //obj

箭頭函數最大的作用是使得this從正常情況下的動態作用域(根據運行位置確定值)變成了靜態作用域(根據定義位置確定值,也就是詞法作用域)。
若想了解得更詳細,可以去閱讀官方文檔: //reactjs.org/docs/handling-events.html

 

Tags: