開發一款React應用離不開大量的事件綁定,
首先一起來回顧回顧React組件事件綁定的幾種方式。
以onClick事件為例
- 最粗魯的方式
class MyComponent extends Component {
handleClick(e) {
this.setState({
key: 'value'
})
}
render() {
return (
<div onClick={this.handleClick.bind(this)}></div>
)
}
}
直接把bind函數寫到render方法里,這是許多新手易犯的錯誤,后果是導致組件狀態變更重新渲染時重復觸發bind函數的執行,嚴重影響性能,要避免這種做法。
- 較好的方式
class MyComponent extends Component {
handleClick = e => {
this.setState({
key: 'value'
})
}
render() {
return (
<div onClick={this.handleClick}></div>
)
}
}
使用ES7類屬性箭頭函數,自動綁定類作用域,需要transform-class-properties支持,缺點是該語法屬于實驗性質,并沒有正式被劃入標準,并且把類方法當作屬性來用并不推薦。
- 最合理的方式
class MyComponent extends Component {
constructor() {
super()
this.handleClick = this.handleClick.bind(this)
}
handleClick(e) {
this.setState({
key: 'value'
})
}
render() {
return (
<div onClick={this.handleClick}></div>
)
}
}
把bind函數寫入constructor方法中,僅當組件初始化時調用,綁定自身作用域,這是最合理的做法,缺點是組件中每增加一個新的事件,就要在constructor方法中綁定一次事件,編碼起來相當麻煩。
思考
比較以上三種方式,結論是在constructor中綁定事件最為合理。
但是,痛點在哪里?
痛點是當一個app包含大量的DOM事件需要訪問this對象時,你需要逐一手動在constructor方法中去綁定事件作用域,大量冗余重復式的代碼。
那么,如何解決這個問題?
我們可以用Babel插件實現作用域綁定自動化,減少沒必要的重復勞動力,簡化工作流程,提升開發效率。
這就是我開發這款Babel插件的起因。
有了這款插件后,你無需手動顯示綁定事件的作用域,this指針永遠指向組件本身。
因此,你可以寫出類似下面的代碼
class MyComponent extends Component {
handleClick(e) {
this.setState({
key: 'value'
})
}
render() {
return (
<div onClick={this.handleClick}></div>
)
}
}
開啟插件高級語法特性后,甚至可以很輕易得傳遞參數
class MyComponent extends Component {
handleClick(e, val) {
this.setState({
key: 'value'
})
console.log(val) // 'hello'
}
render() {
return (
<div onClick={this.handleClick('hello')}></div>
)
}
}
插件幫我們做了什么?
在組件內部查找需要綁定作用域的事件名稱,并把bind語句注入到constructor方法中。
對于傳參語法,自動轉換成箭頭函數,類似(e) => {this.handleClick(e, item)}
實現原理
眾所周知,Babel是一款JavaScript語法轉譯器,工作流程大致可理解為先通過詞法分析把字符串形式的代碼轉換為tokens流,接著進行語法解析,把tokens流轉換為一棵抽象語法樹,然后進入轉換階段,深度遍歷抽象語法樹,對節點進行增刪改,也是你的Babel插件工作的部分,最后解析轉換后的抽象語法樹,輸出生成目標字符串文本。
抽象語法樹中每一個節點可以用一個JavaScript對象來描述,包含節點類型及其它若干屬性,你的Babel插件可以更改這些屬性或者直接替換節點、刪除節點,插入新構造的節點,用插件以訪問者模式注入的思想幫我們做更多的事。
源碼地址: https://github.com/chikara-chan/babel-plugin-react-scope-binding