[譯] React 元件中繫結回撥

hepeguo發表於2016-08-01

原文: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的是事件處理系統。因此handleClickthis上下文和我門想象的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,甚至是你傳遞給事件回撥的函式。

相關文章