在使用 React 時,您不免遇到受控組件和事件處理程序。在自定義組件的構造函數中,咱們須要使用 .bind() 來將方法綁定到組件實例上面。node
class Foo extends React.Component{ constructor( props ){ super( props ); this.handleClick = this.handleClick.bind(this); } handleClick(event){ // 你的事件處理邏輯 } render(){ return ( <button type="button" onClick={this.handleClick}> Click Me </button> ); } } ReactDOM.render( <Foo />, document.getElementById("app") );
在這篇文章中,咱們將探究爲何要這麼作。
若是你對 .bind()
尚不瞭解,推薦閱讀 這篇文章。git
好吧,責怪聽起來有些苛刻。若是按照 React
和 JSX
的語法,咱們並不須要這麼作。其實綁定 this
是 JavaScript 中的語法。github
讓咱們看看,若是不將事件處理程序綁定到組件實例上,會發生什麼:babel
class Foo extends React.Component{ constructor( props ){ super( props ); } handleClick(event){ console.log(this); // 'this' 值爲 undefined } render(){ return ( <button type="button" onClick={this.handleClick}> Click Me </button> ); } } ReactDOM.render( <Foo />, document.getElementById("app") );
若是你運行這個代碼,點擊 「Click Me」 按鈕,檢查你的控制檯,你將會看到控制檯打印出 undefined
,這個值是 handleClick()
方法內部的 this
值。handleClick()
方法彷佛已經丟失了其上下文(組件實例),即 this
值。app
正如我上文提到的,是 JavaScript 的 this
綁定機制致使了上述狀況的發生。在這篇文章中,我不會深刻探討太多細節,可是 這篇文章 能夠幫助你進一步學習在 JavaScript 中 this
的綁定是如何工做的。函數
與咱們討論相關的是,函數內部的 this
的值取決於該函數如何被調用。學習
function display(){ console.log(this); // 'this' 將指向全局變量 } display();
這是一個普通的函數調用。在這種狀況下,display()
方法中的 this
在非嚴格模式下指向 window
或 global
對象。在嚴格模式下,this
指向 undefined
。this
var obj = { name: 'Saurabh', display: function(){ console.log(this.name); // 'this' 指向 obj } }; obj.display(); // Saurabh
當咱們以一個 obj
對象來調用這個函數時,display()
方法內部的 this
指向 obj
。prototype
可是,當咱們將這個函數引用賦值給某個其餘變量並使用這個新變量去調用該函數時,咱們在 display()
中得到了不一樣的this
值。code
var name = "uh oh! global"; var outerDisplay = obj.display; outerDisplay(); // uh oh! global (node)
在上面的例子裏,當咱們調用 outerDisplay()
時,咱們沒有指定一個具體的上下文對象。這是一個沒有全部者對象的純函數調用。在這種狀況下,display()
內部的 this
值回退到默認綁定。如今這個 this
指向全局對象,在嚴格模式下,它指向 undefined。
在將這些函數以回調的形式傳遞給另外一個自定義函數、第三方庫函數或者像 setTimeout
這樣的內置JavaScript函數時,上面提到的判斷方法會特別實用。
考慮下方的代碼,當自定義一個 setTimeout
方法並調用它,會發生什麼。
//setTimeout 的虛擬實現 function setTimeout(callback, delay){ //等待 'delay' 數個毫秒 callback(); } setTimeout( obj.display, 1000 );
咱們能夠分析出,當調用 setTimeout
時,JavaScript 在內部將 obj.display
賦給參數 callback
。
callback = obj.display;
正如咱們以前分析的,這種賦值操做會致使 display()
函數丟失其上下文。當此函數最終在 setTimeout
函數裏面被調用時,display()
內部的 this
的值會退回至默認綁定。
var name = "uh oh! global"; setTimeout( obj.display, 1000 ); // uh oh! global
爲了不這種狀況,咱們可使用 明確綁定方法,將 this 的值經過 bind()
方法綁定到函數上。
bind
方法返回一個新函數,其this
制定爲bind
函數參數對象.
var name = "uh oh! global"; obj.display = obj.display.bind(obj); var outerDisplay = obj.display; outerDisplay(); // Saurabh
如今,當咱們調用 outerDisplay()
時,this
的值指向 display()
內部的 obj
。
即便咱們將 obj.display
直接做爲 callback
參數傳遞給函數,display()
內部的 this
也會正確地指向 obj
。
在本文的開頭,咱們建立了一個類名爲 Foo
的 React 組件。若是咱們不將 this 綁定到事件上,事件內的值會變成 undefined
。
正如我上文解釋的那樣,這是由 JavaScript 中 this 綁定的方式決定的,與React的工做方式無關。所以,讓咱們刪除 React 自己的代碼,並構建一個相似的純 JavaScript 示例,來模擬此行爲。
class Foo { constructor(name){ this.name = name } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display(); // Saurabh //下面的賦值操做模擬了上下文的丟失。 //與實際在 React Component 中將處理程序做爲 callback 參數傳遞類似。 var display = foo.display; display(); // TypeError: this is undefined
咱們不是模擬實際的事件和處理程序,而是用同義代碼替代。正如咱們在 React 組件示例中所看到的那樣,因爲將處理程序做爲回調傳遞後,丟失了上下文,致使 this 值變成 undefined。這也是咱們在這個純 JavaScript 代碼片斷中觀察到的。
你可能會問:「等一下!難道 this 的值不是應該指向全局對象麼,由於咱們是按照默認綁定的規則,在非嚴格模式下運行的它。「
答案是否認的 緣由以下:
類聲明和類表達式的主體以 嚴格模式 執行,主要包括構造函數、靜態方法和原型方法。Getter 和 setter 函數也在嚴格模式下執行。
你能夠在 這裏 閱讀完整的文章。
因此爲了不錯誤,咱們須要像下文這樣綁定 this 的值:
class Foo { constructor(name){ this.name = name this.display = this.display.bind(this); } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display(); // Saurabh var display = foo.display; display(); // Saurabh
咱們不只能夠在構造函數中執行此操做,也能夠在其餘位置執行此操做。考慮這個:
class Foo { constructor(name){ this.name = name; } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display = foo.display.bind(foo); foo.display(); // Saurabh var display = foo.display; display(); // Saurabh
但因爲構造函數是全部初始化發生的地方,所以它是編寫綁定事件語句最佳的位置。
在 React 組件內,咱們有另外兩種定義事件處理程序的方式。
class Foo extends React.Component{ handleClick = () => { console.log(this); } render(){ return ( <button type="button" onClick={this.handleClick}> Click Me </button> ); } } ReactDOM.render( <Foo />, document.getElementById("app") ); // 注意 handleClick寫法: // handleClick = () => {
class Foo extends React.Component{ handleClick(event){ console.log(this); } render(){ return ( <button type="button" onClick={(e) => this.handleClick(e)}> Click Me </button> ); } } ReactDOM.render( <Foo />, document.getElementById("app") );
這兩個都使用了ES6引入的箭頭函數。當使用這些替代方法時,咱們的事件處理程序已經自動綁定到了組件實例上,而且咱們不須要在構造函數中綁定它。
緣由是在箭頭函數的狀況下,this 是有詞法約束力的。這意味它可使用封閉的函數上下文或者全局上下文做爲 this 的值。
在公共類字段語法的例子中,箭頭函數被包含在 Foo 類中或者構造函數中,因此它的上下文就是組件實例,而這就是咱們想要的。
在箭頭函數做爲回調的例子中,箭頭函數被包含在 render() 方法中,該方法由 React 在組件實例的上下文中調用。這就是爲何箭頭函數也能夠捕獲相同的上下文,而且其中的 this 值將正確的指向組件實例。
在 React 的類組件中,當咱們把事件處理函數引用做爲回調傳遞過去,以下所示:
<button type="button" onClick={this.handleClick}>Click Me</button>
事件處理程序方法會丟失其隱式綁定的上下文。當事件被觸發而且處理程序被調用時,this的值會回退到默認綁定,即值爲 undefined,這是由於類聲明和原型方法是以嚴格模式運行。當咱們將事件處理程序的 this 綁定到構造函數中的組件實例時,咱們能夠將它做爲回調傳遞,而不用擔憂會丟失它的上下文。箭頭函數能夠免除這種行爲,由於它使用的是詞法 this 綁定,會將其自動綁定到定義他們的函數上下文。