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