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能作的远远不止如此,无聊的时候能够好好玩一玩。

相关文章
相关标签/搜索