當前位置:
首頁 > 知識 > jsx遇到template-directive

jsx遇到template-directive

「React 和 Vue 哪個更好?」 論壇上經常看到這樣的問題,然後評論區就直接開戰了。也有朋友轉行做前端,問我該學React還是Vue。幾年前,可能確實有必要考慮下到底該選擇哪一個,畢竟前端圈子這麼亂,誰又知道Vue能走多遠?React會不會不維護了呢?可現在兩者生態都很不錯,Vue確實好用,React學習成本也沒有傳聞中那麼高,template很好用,jsx也更靈活。可以兩者都去玩玩,根據個人喜好和項目需要來選擇用哪個。而如果能夠結合兩者的優點,那豈不是很有趣?

我剛轉前端的時候,用的是vue版本好像還是1.0,那時候的感覺就是數據綁定比jquery操作dom省事兒太多了。後來又接觸了React,之後的項目大部分用React寫的,現在偶爾也用vue,總體感覺就是vue單文件組件結構比較清晰,模板指令也很好用,而jsx更加靈活,之前有在react狀態管理部分做一些嘗試,可以像普通function一樣去更新狀態,也一直想在jsx中加上類似vue裡面的模板指令,直到前幾天比較閑,總算實現了 「條件渲染」和 「列表渲染」,結果還算不錯,過程也挺有趣。

先來看看結果吧,以前要根據不同狀態來控制模塊是否顯示,我們大概要寫這樣的代碼:

render(){
const visible = true
return(
<div>
{
visible ? <div>content<div>
: ""
}
</div>
)
}

現在可以這麼玩:

render(){
const visible = true
return(
<div>
<div r-if = {visible}>content</div>
</div>
)
}

另一種常見的場景就是根據一個數組來渲染出一個列表,一般是這麼寫:

render(){
const list = [1, 2, 3, 4, 5]
return(
<div>
{
list.map((item,index)=>(
<div key={index}>{item}</div>
))
}
</div>
)
}

現在可以更簡潔:

render(){
const list = [1, 2, 3, 4, 5]
return(
<div>
<div r-for = {item in list}>{item}</div>
</div>
)
}

以上代碼會自動設置key,值為當前元素的索引。如果你想要自定義key,也可以加上,改成

<div r-for = {(item,index) in list} key = {index+1}>{item}</div>

結果還算不錯吧,代碼更簡短,語義也比較明確,體驗也不必vue裡面的模板指令差,個人感覺在「」裡面寫js有點奇怪。而在{}裡面寫就很自然,就是普通的js代碼塊嘛。

至於實現方法嘛,其實很簡單,總共才幾十行代碼,就是寫了一個babel插件。

我們寫的jsx也是通過babel轉譯成普通js代碼的,然後才能在瀏覽器中運行,而babel編譯主要分為三個階段:解析、轉換、生成目標代碼。解析部分就是將源代碼解析成抽象語法樹ast,轉換過程是對ast做一些處理,而生成目標代碼部分就是將ast再轉換成js代碼。babel-plugin就是在轉換部分做一些工作。

比如對於以下jsx:

<div r-if = { visible }>{content}</div>

轉換成的ast結構大概是:

{
type: "CallExpression",
callee: {},
arguments: {
properties: []
}
}

目標代碼為:

React.createElement(
"div",
{"r-if": visible},
content
)

React.createElement()方法調用對應ast中的CallExpression, React和createElement在callee中可以找到,可以以此來找出createElement(), r-if 等屬性以及第三個參數content在arguments的properties數組中能找到。有了這些信息,我們就可以遍歷ast,找到那些callee為React.createElement的CallExpression, 然後判斷arguments中如果出現了r-if, 就對ast做以下修改:首先移除r-if屬性,避免死循環;然後在CallExpression對應的節點外面再套一層ifStatement, 如此一來,轉換後的ast生成的目標代碼大致如下:

if(visible){
React.createElement(
"div",
{"r-if": visible},
content
)
}

至此,我們的目標就已經達到了,至於r-for列表渲染,原理類似,先找出有r-for屬性的CallExpression, 然後構造一個map方法對應的CallExpression, 當前CallExpression作為參數傳給map方法即可。需要注意的是,在同一次遍歷中解析出 r-if 和 r-for 的話,需要把map放在外層,ifStatement放在裡面,如果覺得這樣做結構比較混亂,可以拆分成不同的插件。

最後再說一下babel-plugin的寫法,其實也就是一個方法:

module.exports = function ({ types: t }) {
return {
visitor: {
CallExpression(path) {
// 在這裡通過修改path來修改ast。
},
Identifier(path) {

}
}
}
}

types類型為babel-types, 提供了一些類似loadash的操作方法,比如做一些判斷、構造節點。visitor裡面寫對應類型節點的遍歷方法, 比如遍歷標識符類型的就寫在Identifier中,方法調用就寫在CallExpression中。

本文中提到的r-if 和 r-for 已經寫成了一個插件,可以在github倉庫中找到:https://github.com/evolify/babel-plugin-react-directive 同時也發布到了npm倉庫,可以直接安裝:

yarn add --dev babel-plugin-react-directive

然後在.babelrc中配置即可:

{
"plugins": [
"react-directive"
]
}

我想要的目的已經達到了,但這並未結束,才剛剛開始,還可以實現其他的一些指令,比如r-if 是模塊渲染或者不渲染的,我們經常也會遇到這種需求:只是單純的控制元素可見或者不可見,但元素還是佔用空間的,也就是控制visibility, 這也可以寫成一個指令。而babel能做的遠遠不止如此,無聊的時候可以好好玩一玩。

jsx遇到template-directive

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

apache的commons-email 類庫開發示例
排序演算法總結

TAG:程序員小新人學習 |