React Bind Handle的思考

文章來自我我的的 Github
)

在平時的開發裏面,總會碰到handle綁定的問題。若是你和我同樣懶或者思考過,你會以爲這個過程實在是太煩了吧。這裏記錄一下個人思路和歷程。git

這裏以一個按鈕的點擊事件來作示例。github

class App extends React.Components {
    state = {
        count: 0
    }

    clickHandler () {
        const count = this.state.count + 1
        this.setState({ count })
    }

    render() {
         return (
            <button>
                Click me to show something in dev tools
            </button>
        )
    }
}

這個例子的目的是點擊按鈕觸發clickHandler來讓計數器加1。咱們能夠用兩種不一樣的方式來觸發這個handle,由於咱們使用了this.setState,因此咱們都必需要給函數綁定this。亦或是使用箭頭函數處理這個地方。typescript

直接在jsx裏面bind(this)

<button onClick={this.clickHandler.bind(this)}>
    Click me to show something in dev tools
</button>

嗯 這個的確能夠。可是寫起來很是的長看起來也挺醜的。有個問題是每次重渲染的時候都會從新bind一次函數,對於比較大的列表來講這個地方很是不可取。babel

使用箭頭函數

clickHandler改爲以下的範式。ide

clickHandler = () => {
    const count = this.state.count + 1
    this.setState({ count })
}

// render ...
<button onClick={this.clickHandler)}>
    Click me to show something in dev tools
</button>

誒這樣看起來會好不少誒。可是若是你有強迫症你會發現一件事情。若是咱們加上生命週期函數和一些其餘的handler ... 好比這樣。函數

componentDidMount () {}
componentWillMount () {}
componentWillUpdate () {}

clickHandler = () => {
    const count = this.state.count + 1
    this.setState({ count })
}

antoherHandle = () => {}

你會發現這裏生命週期函數和handler的寫法不同。可是你的確可讓它們變得同樣,好比把生命週期函數改爲箭頭函數。但是這看起來不會以爲很怪異嗎。畢竟你一直以來都不是這麼作的。性能

除此以外箭頭函數沒法被繼承,這意味着若是你的子組件須要繼承函數,這將會致使沒法作到。更加須要注意的東西是沒法繼承帶來的性能問題。這會致使每次建立組件都會建立新的方法致使額外的開銷(由於箭頭函數的實現實際上是直接在constructor函數裏丟方法),若是是經過繼承,那麼它們這些方法老是來自同一個prototype,js編譯器是會作優化的。測試

詳細文章能夠看這一篇Arrow Functions in Class Properties Might Not Be As Great As We Thinkfetch

在構造器裏面使用bind(this)

經過構造器來寫綁定函數其實看起來是不錯的優化

constructor (props) {
    super(props)
    this.clickHandler = this.clickHandler.bind(this)
    this.antoherHandle = this.antoherHandle.bind(this)
}

既解決了性能(內存)的問題。還能作不少有意思的事情好比說,利用現有的方法添加更有語義化的方法。

constructor (props) {
    super(props)
    this.clickHandler = this.clickHandler.bind(this)
    this.antoherHandle = this.antoherHandle.bind(this)
    this.clickWithOne = this.clickHandler.bind(this, 1)
}

這樣就能產生每次都會傳參數1的新事件。看起來的確是還不錯。可是仍然有問題。當你的方法線性的增長的時候,若是有三個四個五個六個的時候,你可能須要一個一個的綁定。添加它們到構造函數裏面,更糟糕的多是經過複製粘貼之前寫的方法,你會綁定錯誤的函數。就像這樣。

constructor (props) {
    super(props)
    this.clickHandler = this.clickHandler.bind(this)
    this.antoherHandle = this.antoherHandle.bind(this)
    this.clickWithOne = this.antoherHandle.bind(this, 1)
}

你必須在運行的時候才知道你的clickWithOne綁定的實際上是antoherHandle。若是你沒測試過,那麼極可能就會出現一些你難以理解的問題或者bug。

自動綁定

若是你動腦想一想會發現能夠寫一個autobind的方法來自綁定函數呀。可是你很懶沒有去寫,你經過github搜索到了一個叫作React-autobind的庫。看起來好像還不錯。

constructor(props) {
  super(props);
  autoBind(this);
}

甚至能夠不綁定某些方法。

constructor(props) {
  super(props);
  autoBind(this, {
    wontBind: ['leaveAlone1', 'leaveAlone2']
  });
}

或者指定只綁定某些方法。

constructor(props) {
  super(props);
  autoBind(this, {
    bindOnly: ['myMethod1', 'myMethod2']
  });
}

看起來彷佛是妙極了。可是你會發現這個寫法其實仍是很繁瑣啊。要寫一坨東西。。打開源碼看一眼你會發現有一個默認的wonbind列表。

let wontBind = [
  'constructor',
  'render',
  'componentWillMount',
  'componentDidMount',
  'componentWillReceiveProps',
  'shouldComponentUpdate',
  'componentWillUpdate',
  'componentDidUpdate',
  'componentWillUnmount'
];

表示不須要自動綁定的函數的名字。可是這個列表很是的糟糕,由於隨着React版本的提高,某些鉤子和方法都會被廢棄,隨着時間可能還會增長增多的方法。

這個庫也好久沒更新了。差評仍是放棄吧。。。

Autobind-decorator

若是你瞭解過ES7decorator。你會發現上面的寫法徹底可使用decorator的形式表示,而且這個庫也支持在typescript上面使用。而且結構會很是的清晰。因而你找到了autobind-decorator這個庫。它能幫助到咱們,給咱們想要的東西,文檔一開始就告訴咱們。

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

// After:
<button onClick={ this.handleClick }></button>

用以前...用以後的樣子,很好就是咱們要的。 這個庫有個缺點就是必須的IE11+以上的版本才支持,可是這其實也還好。

另外就是你的開啓decorator的支持在babel的配置裏面。

{
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
  ]
}

咱們來看看推薦的用法。

import {boundMethod} from 'autobind-decorator'

class Component {
  constructor(value) {
    this.value = value
  }

  @boundMethod
  method() {
    return this.value
  }
}

let component = new Component(42)
let method = component.method // .bind(component) isn't needed!
method() // returns 42

給方法綁定this,而不是整個類,這麼作是更加合理的。由於不是每一個方法都須要用到this的。如Dan所說。

It is unnecessary to do that to every function. This is just as bad as autobinding (on a class). You only need to bind functions that you pass around. e.g. onClick={this.doSomething}. Or fetch.then(this.handleDone) -- Dan Abramov‏

既能夠在函數上,也能夠在類上使用的@autobind

import autobind from 'autobind-decorator'

class Component {
  constructor(value) {
    this.value = value
  }

  @autobind
  method() {
    return this.value
  }
}

let component = new Component(42)
let method = component.method // .bind(component) isn't needed!
method() // returns 42

// Also usable on the class to bind all methods
// Please see performance if you decide to autobind your class
@autobind
class Component { }

只能做用於類的@boundClass,咱們不免也會有全都須要綁定到this的狀況這時候咱們直接boundClass會更加的簡潔。

import {boundClass} from 'autobind-decorator'

@boundClass
class Component {
  constructor(value) {
    this.value = value
  }

  method() {
    return this.value
  }
}

let component = new Component(42)
let method = component.method // .bind(component) isn't needed!
method() // returns 42

缺點也是有的,並不能像constructor那樣本身隨隨便便的定不一樣的方法名經過原有的方法,必須的寫出一個新的,可是這是小問題,無傷大雅。而且descorator並無成爲標準,可是其實也差很少了,並不擔憂。

結語

這裏的全部的解決思路都各有千秋吧。怎麼取捨仍是看本身,這裏就不一一列出來各自的對比了 ,於我我的而言會偏好Autobind-decorator,認爲這是全部解決方案裏面最好的一個了,可是要引入一個額外的依賴仍是有點麻煩。

相關文章
相關標籤/搜索