文章來自我個人的Github
在平時的開發裡面,總會碰到handle
繫結的問題。如果你和我一樣懶或者思考過,你會覺得這個過程實在是太煩了吧。這裡記錄一下我的思路和歷程。
這裡以一個按鈕的點選事件來做示例。
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
。亦或是使用箭頭函式
處理這個地方。
直接在jsx
裡面bind(this)
<button onClick={this.clickHandler.bind(this)}>
Click me to show something in dev tools
</button>
複製程式碼
嗯 這個的確可以。但是寫起來非常的長看起來也挺醜的。有個問題是每次重渲染
的時候都會重新bind
一次函式,對於比較大的列表來說這個地方非常不可取。
使用箭頭函式
把clickHandler
改成如下的正規化。
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 Think。
在構造器裡面使用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
如果你瞭解過ES7
的decorator
。你會發現上面的寫法完全可以使用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}
. Orfetch.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
,認為這是所有解決方案裡面最好的一個了,但是要引入一個額外的依賴還是有點麻煩。