vue2-代码生成器

在vue2-AST抽象语法树 https://www.jianshu.com/p/d7822b42a3e3
中 说了一下 把模板 --> ast抽象语法树 --> 渲染函数;

其实在里面 详细 是 模板 ——> 模板编译 ——> 渲染函数

其中 模板编译 包括 三个模块: 解析器 -> 优化器 -> 代码生成器

  • 解析器: 把模板解析成AST https://www.jianshu.com/p/83568cff95e1
  • 优化器:遍历AST 标记静态节点
  • 代码生成器: 使用AST 生成渲染函数

现在开始代码生成器

代码生成器的作用是将AST转换成渲染函数中的内容,这个内容可以称为代码字符串。

代码字符串可以被包装再函数中执行,这个函数就是我们说的渲染函数。

比如:

Hello {{name}}

转换为AST并被优化器优化之后

{
  'type': 1,
  'tag': 'div',
  'attrsList': [
    {
      'name': 'id',
      'value': 'el'
    }
  ],
  'attsMap': {
    'id': 'el'
  },
  'children': [
    {
      'type': 2,
      'expression': '"Hello "+_s(name)',
      'text': 'Hello {{name}}',
      'static': false  // 静态节点标记
    }
  ],
  'plain': false, // true 则没有属性
  'attrs': [
    {
      'name': 'id',
      'value': '"el"'
    }
  ],
  'static': false, // 静态节点标记
  'staticRoot': false // 静态根节点标记
}

代码生成器可以把这个AST转换为字符串:

 `with (this) {
   return _c(
     'div',
     {
       attrs: {'id': 'el'},
     }, 
     [
        _v('Hello '+ _s(name))
      ]
    )
  }`

其中_s 是下面toString函数的别名

function toString(val) {
  return val === null ?
    '' :
    typeof val === 'object' ?
      JSON.stringify(val, null, 2) :  // 返回值文本在每个级别缩进2个的空格
      String(val)
}

代码字符串中 _c其实是 createElement的别名, createElement是 虚拟DOM中提供的方法,它的作用是创建虚拟节点, 接受三个参数, 分别是:

  • 标签名
  • 一个包含模板相关属性的数据对象
  • 子节点列表

调用createElement 方法,我们可以得到一个VNode.

渲染函数可以生产VNode 的原因是: 渲染函数执行了createElement方法创建了一个VNode.

节点有三种类型 分别对应三种不同的创建方法和别名

类型 创建方法 别名
元素节点 createElement _c
文本节点 createTextVNode _v
注释节点 createEmptyVNode _e

代码生成器的原理

元素节点
生成元素节点 其实就是生成一个_c的函数调用字符串

  function genElement(el, state){
    // 如果 el.plain是true, 则说明 节点没有属性
    const data = el.plain ? undefined: genData(el, state)

    const children = genChildren(el, state)

    code = `_c('${el.tag}'${
      data ? `, ${data}` : '' // data
      }${
        children ? `,${children}`: '' //children
      })`
      return code
  }

代码中el的plain 属性是编译时发现的, 如果节点没有属性,那么plain设置为true,我们可以通过plain 来判断 是否需要获取节点的属性属性。

其中 genData 和 genChildren 分别获取 data和children。最后拼接好
“_c(tagName, data, children)”

  function genData(el, state){
    let data = '{'
    // key
    if(el.key){
      data += `key:${el.key},`
    }
    // ref
    if(el.ref){
      data += `ref:${el.ref},`
    }
    // pre
    if(el.pre){
      data += `pre:${el.pre},`
    }
    // ...  还有其他很多这种情况
    data = data.replace(/,$/, '') + '}'
    return data
  }

genData 其实也是拼接字符串 ,发现节点存在哪些属性数据,就拼接到data中,最后返回

genChildren的 逻辑也是拼接字符串,通过循环子节点列表,根据不同 的子节点类型 生成不同的 节点字符串 将其 拼接到一起

 function genChildren(el, state){
    const children = el.children
    if(children.length){
      return `[${children.map( c => genNode(c, state).join(','))}]`
    }
    function genNode(node, state){
      // 元素
      if(node.type === 1){
        return genElement(node, state)
      }
      // 注释节点
      if(node.type === 3 && node.isComment){
        return genComment(node)
      }
      // 文本节点
       else {
         return genText(node)
       }
    }
  }

文本节点
生成文本节点很简单, 把文本放到_v函数的参数中即可

  function genText(node){
    return `_v(${node.type === 2
      ? node.expression 
      : JSON.stringify(node.text)
    })`
  }

如果是动态文本比如 my name is {{name}}, 则使用expression.如果是静态文本,这使用text;

这里text 使用JSON.stringify方法 的原因是:
expression 的文本是这样的 ' "Hello " + _s(name) '
而 text 的文本 是 "Hello zs"
我们希望 静态文本是 ' "Hello zs" '

所以这里用个JSON.stringify给文本包装一层字符串。

注释节点

  function genComment(node){
    return `_e(${JSON.stringify(node.text)})`
  }

注释节点与文本节点相同, 只需要把文本放在_e的参数中 即可。

代码生成器 其实就是 字符串拼接 的过程。 通过递归AST 生成字符串,最先生成 根节点, 然后在 子节点字符串生成后, 将其拼接到 根节点的参数中, 子节点 的子节点 拼接在 子节点的参数中, 一层一层的拼接,知道拼接成完整的字符串。

你可能感兴趣的:(vue2-代码生成器)