VUE源码学习第九篇--编译(generate)

一、总述

     经过前面的两步,将template转成了AST的模型树,接下来再将AST模型树转成render表达式。在第六章节我们讲过,执行render表达式后,生成了vnode。我们用前面的例子看下生成的render表达式的格式。

with(this){
  return 
     _c('div',{
         attrs:{"id":"app"}
     },
     [_c('ul',_l((items),function(item){
       return _c('li',
                 [_v("\n      itemid:"+_s(item.id)+"\n    ")]
       )}
      )
     )]
   )}

render表达式总体上由三部组成,其结构图如下:

_c(
  // 1、标签
  'div',
  //2、模板相关属性的数据对象 
  {
   ...  
  },
  //3、子节点,循环其模型
  [
    _c(...)
  ]
)

如不熟悉render表达式格式的请参考官方文档渲染函数 & JSX。在第五章节我们讲过initRender方法,有对_c方法的定义

vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

_l表示创建节点列表,_v表示创建text文本,其相关的方法定义在src/core/instance/render-helpers/index.js中

export function installRenderHelpers (target: any) {
  target._o = markOnce
  target._n = toNumber
  target._s = toString
  target._l = renderList
  target._t = renderSlot
  target._q = looseEqual
  target._i = looseIndexOf
  target._m = renderStatic
  target._f = resolveFilter
  target._k = checkKeyCodes
  target._b = bindObjectProps
  target._v = createTextVNode
  target._e = createEmptyVNode
  target._u = resolveScopedSlots
  target._g = bindObjectListeners
}

接下来我们看render表示式是如何生成的。

二、generate

generate方法位于/src/compiler/codegen/index.js中

export function generate (
  ast: ASTElement | void,
  options: CompilerOptions
): CodegenResult {
  const state = new CodegenState(options)
  //核心部分,生成render表达式字符串主体
  const code = ast ? genElement(ast, state) : '_c("div")'
  return {
    //最外层用with(this)包裹
    render: `with(this){return ${code}}`,
    //被标记为 staticRoot 节点的 VNode 就会单独生成 staticRenderFns
    staticRenderFns: state.staticRenderFns
  }
}

该方法逻辑较简单,最核心部分就是调用genElement生成render表达式字符串。

export function genElement (el: ASTElement, state: CodegenState): string {
  //对一些标签属性的处理
  if (el.staticRoot && !el.staticProcessed) {
    return genStatic(el, state)
  } else if (el.once && !el.onceProcessed) {
    return genOnce(el, state)
  } else if (el.for && !el.forProcessed) {
    return genFor(el, state)
  } else if (el.if && !el.ifProcessed) {
    return genIf(el, state)
  } else if (el.tag === 'template' && !el.slotTarget) {
    return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {
    return genSlot(el, state)
  } else {
    // component or element
    let code
    //组件的处理
    if (el.component) {
      code = genComponent(el.component, el, state)
    } else {
      //核心的body部分
      //1、生成节点的数据对象data的字符串
      const data = el.plain ? undefined : genData(el, state)
      //2、查找其子节点,生成子节点的字符串
      const children = el.inlineTemplate ? null : genChildren(el, state, true)
      //3、将tag,data,children拼装成字符串
      code = `_c('${el.tag}'${
        data ? `,${data}` : '' // data
      }${
        children ? `,${children}` : '' // children
      })`
    }
    // module transforms
    for (let i = 0; i < state.transforms.length; i++) {
      code = state.transforms[i](el, code)
    }
    return code
  }
}

该方法先对一些特殊属性标签进行处理,本章第三部分将详细描述,组件部分我们也放到后面章节介绍。我们直接看其核心的body部分:

1、生成节点的数据对象字符串,比如样例中的div节点的attrs属性。

2、查找其子节点,调用genChildren生成子节点的字符串,比如样例中ul。

3、最后将tag,data,children,通过_c封装完成节点表达式。

在介绍genChildren方法前,我们先思考下,如果是我们来实现该方法,该如何做呢。因为整个AST是树型结构,所以必然是采用递归的方式,一层一层生成子节点的字符串,直至到叶节点。我们直接看下genChildren核心的代码段

export function genChildren (
  el: ASTElement,
  state: CodegenState,
  checkSkip?: boolean,
  altGenElement?: Function,
  altGenNode?: Function
): string | void {
  const children = el.children
  ...

    //拼装子节点的字符串
    return `[${children.map(c => gen(c, state)).join(',')}]${
      normalizationType ? `,${normalizationType}` : ''
    }`
  }
}

子节点可能有多个,采用循环的方式,然后调用gen(即genNode)方法生成字符串。

function genNode (node: ASTNode, state: CodegenState): string {
  if (node.type === 1) {//1、对于node节点,调用genElement进行递归
    return genElement(node, state)
  } if (node.type === 3 && node.isComment) {//2、注释
    return genComment(node)
  } else {//3、叶节点,生成text文本字符串
    return genText(node)
  }
}

该方法与我们的思考不谋而合。

1、对于type=1(节点类型)的元素,调用genElement进行递归。如样例中的div,ul,li等。

2、其他类型作为叶节点处理,一般为文本类型。如样例中的叶节点"itemid:{{item.id}}",生成的字符串如下:

_v("\n      itemid:"+_s(item.id)+"\n    ")

三、特定的标签属性处理

在genElement方法中,对一些特定的标签属性进行处理。我们介绍其中的几个

1、genFor

针对包含v-for属性的标签,将调用该方法生成字符串,如样例中的"li"节点,生成的表达式中将由下面的字符进行包裹

_l((items),function(item){
       return ...
})

实际就是将节点的正常字符串包裹在_l方法中,在执行时调用_l方法,循环生成多个该类型的节点。我们来看下该方法:

export function genFor (
  el: any,
  state: CodegenState,
  altGen?: Function,
  altHelper?: string
): string {
  const exp = el.for
  const alias = el.alias
  const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
  const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''

  ...
  //1、标记已处理
  el.forProcessed = true // avoid recursion
  //2、function...return...包裹字符串,调用genElement继续节点字符串生成
  return `${altHelper || '_l'}((${exp}),` +
    `function(${alias}${iterator1}${iterator2}){` +
      `return ${(altGen || genElement)(el, state)}` +
    '})'
}

     首先标记该节点的v-for属性已处理(防止进入死循环),用function...return...包裹字符串并返回,该节点的字符串调用genElement继续生成。

当再次进入到genElement方法,由于el.forProcessed为true,所以跳过了genFor,执行核心的body部分,生成节点的字符串。

} else if (el.for && !el.forProcessed) {
    return genFor(el, state)

2、genIf

对于包含v-if属性的节点,将调用genIf生成字符串,根据条件的个数,也将生成一段多元表达式字符串将节点的字符串包裹,其样式如下:

(xxx)?....:(ttt)?...:...
export function genIf (
  el: any,
  state: CodegenState,
  altGen?: Function,
  altEmpty?: string
): string {
  el.ifProcessed = true // avoid recursion
  return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}

function genIfConditions (
  conditions: ASTIfConditions,
  state: CodegenState,
  altGen?: Function,
  altEmpty?: string
): string {
  if (!conditions.length) {
    return altEmpty || '_e()'
  }
  //对于多个condition,将调用genIfConditions进行递归生成二元表达式
  const condition = conditions.shift()
  if (condition.exp) {
    //生成本次条件的二元表达式
    return `(${condition.exp})?${
      genTernaryExp(condition.block)
    }:${
      //递归生成后续的二元表达式
      genIfConditions(conditions, state, altGen, altEmpty)
    }`
  } else {
    return `${genTernaryExp(condition.block)}`
  }

  // v-if with v-once should generate code like (a)?_m(0):_m(1)
  function genTernaryExp (el) {
    return altGen
      ? altGen(el, state)
      : el.once
        ? genOnce(el, state)
        : genElement(el, state)//生成节点的字符串
  }
}

genIf主要执行了genIfConditions方法,根据condition的个数,递归生成二元表达式,最后拼装成一个多元的表达式。

对于该节点的字符串内容生成,递归调用了genElement方法。过程与genFor类似。

3、genStatic

对于静态的根节点,将调用genStatic生成字符串

function genStatic (el: ASTElement, state: CodegenState): string {
  el.staticProcessed = true
  state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)
  return `_m(${
    state.staticRenderFns.length - 1
  }${
    el.staticInFor ? ',true' : ''
  })`
}

字符串将放置到staticRenderFns数组中,并返回_m(xx)表达式。

genSlot我们后续再介绍,genOnce处理逻辑类似,这里就不详细介绍。

四、总结

generate主要就是将AST模型转成render表达式,其中采用了大量的递归调用,主要的流程如下:

发布了33 篇原创文章 · 获赞 95 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/tcy83/article/details/89058191