React騷操做——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裏面的模板指令,直到前幾天比較閒,總算實現了 「條件渲染」和 「列表渲染」,結果還算不錯,過程也挺有趣。vue

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

render(){
    const visible = true
    return(
        <div>
            {
                visible ? <div>content<div>
                        : ''
            }
        </div>
    )
}
複製代碼

 如今能夠這麼玩:jquery

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

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

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

 如今能夠更簡潔:github

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

 以上代碼會自動設置key,值爲當前元素的索引。若是你想要自定義key,也能夠加上,改爲npm

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

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

 至於實現方法嘛,其實很簡單,總共才幾十行代碼,就是寫了一個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倉庫中找到:github.com/evolify/bab… 同時也發佈到了npm倉庫,能夠直接安裝:

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

 而後在.babelrc中配置便可:

{
    "plugins": [
        "react-directive"
    ]
}
複製代碼

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

相關文章
相關標籤/搜索