vue源码分析-9-模版编译-generate(ast, options)

1,074 阅读2分钟

parse方法生成ast之后,又调用了generate方法将ast转化为render函数

// 生成渲染函数字符串
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }

generate(ast, options)

generate主要调用了genElement方法,生成render字符串

export function generate (
  ast: ASTElement | void,
  options: CompilerOptions
): CodegenResult {
  const state = new CodegenState(options)
  const code = ast ? genElement(ast, state) : '_c("div")'
  return {
    render: `with(this){return ${code}}`,
    staticRenderFns: state.staticRenderFns
  }
}

genElement(ast, state)

export function genElement (el: ASTElement, 
state: CodegenState): string {
  if (el.parent) {
    el.pre = el.pre || el.parent.pre
  }

  if (el.staticRoot && !el.staticProcessed) {
    // 如果是一个静态的树, 如 <div id="app">123</div>
    // 生成_m()方法
    // 静态的渲染函数被保存至staticRenderFns属性中
    return genStatic(el, state)
  } else if (el.once && !el.onceProcessed) {
    // v-once 转化为_o()方法
    return genOnce(el, state)
  } else if (el.for && !el.forProcessed) {
    // _l()
    return genFor(el, state)
  } else if (el.if && !el.ifProcessed) {
    // v-if 会转换为表达式
    return genIf(el, state)
  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
    // 如果是template,处理子节点
    return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {
    // 如果是插槽,处理slot
    return genSlot(el, state)
  } else {
    // component or element
    let code
    // 如果是组件,处理组件
    if (el.component) {
      code = genComponent(el.component, el, state)
    } else {
      let data
      if (!el.plain || (el.pre && state.maybeComponent(el))) {
        data = genData(el, state)
      }

      const children = el.inlineTemplate ? null : genChildren(el, state, true)
      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
  }
}

genStatic(el, state)

genStatic会将ast转化为_m()方法

例如模版是纯静态dom

<div id="app">
    <p>测试</p>
</div>

转换后的render函数如下

image

render函数是一个_m()方法,真正的静态render函数在staticRenderFns属性中

genOnce(el, state)

如果v-once在v-for中,那么就会生成_o()方法, 否则将其视为静态节点

例如template如下

v-once不在v-for中,以静态节点处理

<div id="app">
    <p v-once>{{test}}</p>
</div>

转换后如下

image

v-once在v-for中

<div id="app">
        <div :key="index" v-for="(item,index) in array">
            <p v-once>{{item}}</p>
        </div>
    </div>

转换后如下

image

genFor(el, state)

v-for会转换为_l()

不再赘述示例

genIf(el, state)

genIf会将v-if转换为表达式,示例如下

转换后如下

image

genComponent(el.component, el, state)

如果是组件 或者 是标签元素的话, 那么会被转化为_c()

示例如下

image

<div id="app">
        <comp1></comp1>
    </div>

转换后如下所示

总结

generate方法内部逻辑还是很复杂的,但仅做了一件事情,就是将ast转化为render函数的字符串,形成一个嵌套结构的方法,模版编译生成的_c(),_m(),_l等等其实都是生成vnode的方法,在执行vue.$mount方法的时候,会调用vm._update(vm._render(), hydrating)方法,此时_render()中方法会执行生成的render()函数,执行后会生成vnode,也就是虚拟dom节点

以下是生成vnode相关的方法,后续讲解

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
  // 创建一个文本的vnode
  target._v = createTextVNode
  target._e = createEmptyVNode
  target._u = resolveScopedSlots
  target._g = bindObjectListeners
  target._d = bindDynamicKeys
  target._p = prependModifier
}