原文:Binding callbacks in React components
在元件中給事件繫結處理函式是很常見的,比如說每當使用者點選一個button的時候使用console.log
列印一些東西。
class DankButton extends React.Component {
render() {
return <button onClick={this.handleClick}>Click me!</button>
}
handleClick() {
console.log(`such knowledge`)
}
}
很好,這段程式碼會滿足你的需求,那現在如果我想在handleClick()
內呼叫另外一個方法,比如logPhrase()
class DankButton extends React.Component {
render() {
return <button onClick={this.handleClick}>Click me!</button>
}
handleClick() {
this.logPhrase()
}
logPhrase() {
console.log(`such gnawledge`)
}
}
這樣竟然不行,會得到如下的錯誤提醒
TypeError: this.logPhrase is not a function at handleClick (file.js:36:12)
當我們把handleClick
繫結到 onClick
的時候我們傳遞的是一個函式的引用,真正呼叫handleClick
的是事件處理系統。因此handleClick
的this
上下文和我門想象的this.logPhrase()
是不一樣的。
這裡有一些方法可以讓this
指向DankButton元件。
不好的方案 1:箭頭函式
箭頭函式是在ES6中引入的,是一個寫匿名函式比較簡潔的方式,它不僅僅是包裝匿名函式的語法糖,箭頭函式沒有自己的上下問,它會使用被定義的時候的this
作為上下文,我們可以利用這個特性,給onClick
繫結一個箭頭函式。
class DankButton extends React.Component {
render() {
// Bad Solution: An arrow function!
return <button onClick={() => this.handleClick()}>Click me!</button>
}
handleClick() {
this.logPhrase()
}
logPhrase() {
console.log(`such gnawledge`)
}
}
然而,我並不推薦這種解決方式,因為箭頭函式定義在render
內部,元件每次重新渲染都會建立一個新的箭頭函式,在React中渲染是很快捷的,所以重新渲染會經常發生,這就意味著前面渲染中產生的函式會堆在記憶體中,強制垃圾回收機制清空它們,這是很花費效能的。
不好的方案 2:this.handleClick.bind(this)
另外一個解決這個問題的方案是,把回撥繫結到正確的上下問this
class DankButton extends React.Component {
render() {
// Bad Solution: Bind that callback!
return <button onClick={this.handleClick.bind(this)}>Click me!</button>
}
handleClick() {
this.logPhrase()
}
logPhrase() {
console.log(`such gnawledge`)
}
}
這個方案和箭頭函式有同樣的問題,在每次render
的時候都會建立一個新的函式,但是為什麼沒有使用匿名函式也會這樣呢,下面就是答案。
function test() {}
const testCopy = test
const boundTest = test.bind(this)
console.log(testCopy === test) // true
console.log(boundTest === test) // false
.bind
並不修改原有函式,它只會返回一個指定執行上下文的新函式(boundTest和test並不相等),因此垃圾回收系統仍然需要回收你之前繫結的回撥。
好的方案:在建構函式(constructor)中bind handleClick
仍然使用 .bind
,現在我們只要繞過每次渲染都要生成新的函式的問題就可以了。我們可以通過只在建構函式中繫結回撥的上下問來解決這個問題,因為建構函式只會呼叫一次,而不是每次渲染都呼叫。這意味著我們沒有生成一堆函式然後讓垃圾回收系統清除它們。
class DankButton extends React.Component {
constructor() {
super()
// Good Solution: Bind it in here!
this.handleClick = this.handleClick.bind(this)
}
render() {
return <button onClick={this.handleClick}>Click me!</button>
}
handleClick() {
this.logPhrase()
}
logPhrase() {
console.log(`such gnawledge`)
}
}
很好,現在我們的函式被繫結到正確的上下文,而且不會在每次渲染的時候建立新的函式。
如果你使用的是React.createClass
而不是ES6的classes,你就不會碰到這個問題,createClass
生成的元件會把它們的方法自動繫結到元件的this
,甚至是你傳遞給事件回撥的函式。