Vue 进阶 [九] Vue 源码剖析03 模板编译 & 组件化机制

业精于勤 荒于嬉

模板编译

概述

模板编译的主要目标是将模板(template)转换为渲染函数(render)

template   =>   render()

模板编译的必要性

Vue 2.0 需要用到VNode 描述视图以及各种交互,手写显然不切实际,因此用户需要编写类似HTML 代码的Vue模板,通过编译器将模板转换为可返回VNode 的 render 函数。

整体流程

如果用户设置了template 或者是el 选项,最终就会执行编译函数,compileToFunctions

若指定 template el 选项,则会执行编译, platforms\web\entry-runtime-with-compiler.js
 

编译过程

编译分为三步:解析 ,优化,生成; src\compiler\index.js

export const createCompiler = createCompilerCreator(function baseCompile(
  template: string,
  options: CompilerOptions
): CompiledResult {
  // 1、解析:将模板转换为对象 AST(抽象语法树)
  const ast = parse(template.trim(), options)
  //2、标记静态节点(页面中不会变的地方)
  if (options.optimize !== false) {
    optimize(ast, options)
  }
  //3、代码生成 转换 ast 为代码字符串 new Function(code) 这样就可以得到一个真正的render 函数了
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})
解析-parse
 
解析器将模板解析为抽象语法树,基于AST可以做优化或者代码生成工作。
调试查看得到的AST, /src/compiler/parser/index.js, 结构如下:

解析器内部分了 HTML 解析器 文本解析器 过滤器解析器 ,最主要是 HTML 解析器
 
优化-optimize
 
优化器的作用是在AST中找出静态子树并打上标记。静态子树是在AST中永远不变的节点,如纯文本节点
 
标记静态子树的好处:
  • 每次重新渲染,不需要为静态子树创建新节点
  • 虚拟DOM 中 patch 时,可以跳过静态子树

代码实现,src/compiler/optimizer.js - optimize

标记结束:

代码生成 - generate

将AST 转换成渲染函数中的内容,即代码字符串。

generate方法生成渲染函数代码,src/compiler/codegen/index.js

生成的代码形如
`_c('div',{attrs:{"id":"demo"}},[ 
_c('h1',[_v("Vue.js测试")]),
 _c('p',[_v(_s(foo))]) 
 ])`
 
其中 几个关键指令的解析 如 v-if  v-for 的解析生成
    // 关键指令解析
      if (inVPre) {
        processRawAttrs(element)
      } else if (!element.processed) {
        // structural directives
        processFor(element)
        processIf(element)
        processOnce(element)
      }
export function genElement (el: ASTElement, state: CodegenState): string {
  if (el.parent) {
    el.pre = el.pre || el.parent.pre
  }

  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 && !state.pre) {
    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 {
      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
  }
}

组件化机制

组件声明

Vue.component()

initAssetRegisters(Vue) src/core/global-api/assets.js

组件注册使用extend方法将配置转换为构造函数并添加到components选项


组件实例创建及挂载

 

整体流程

首先创建的是根组件,首次 _render() 时,会得到整棵树的 VNode 结构
整体流程: new Vue() => $mount() => vm._render() => createElement() => createComponent() =》 patch = createElm = createComponent()

创建自定义组件VNode

  let vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
          context
        )
      }
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  if (Array.isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns)
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    return createEmptyVNode()
  }
}

_createElement src\core\vdom\create-element.js

_createElement实际执行VNode创建的函数,由于传入tag是非保留标签,因此判定为自定义组件通过createComponent去创建

createComponent src/core/vdom/create-component.js
 
创建组件 VNode ,保存了上一步处理得到的组件构造函数, props ,事件等
 

注意组件钩子安装和组件tag指定规则

创建自定义组件实例

根组件执行更新函数时,会递归创建子元素和子组件,入口createElm

createEle() core/vdom/patch.js 

首次执行_update()时,patch()会通过createEle()创建根元素,子元素创建研究从这里开始

createComponent core/vdom/patch.js

     // 组件实例创建、挂载
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        i(vnode, false /* hydrating */ )
      }
      // after calling the init hook, if the vnode is a child component
      // it should've created a child instance and mounted it. the child
      // component also has set the placeholder vnode's elm.
      // in that case we can just return the element and be done.
      if (isDef(vnode.componentInstance)) {
        // 元素引用指定vnode.elm 元素属性创建等
        initComponent(vnode, insertedVnodeQueue)
        // 插入到父元素
        insert(parentElm, vnode.elm, refElm)
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true
      }

注:

组件创建顺序 自上而下

组件挂载顺序 自下而上

总结

Vue 源码的学习进阶目前先到这里,还需要自己在多看多分析多尝试,Vue源码的学习使我们能够深入理解原理,解答很多开发中的疑惑、规避很多潜在的错误,写出更好的代码。学习大神的代码,能够学习编程思想,设计模式,训练基本功能,提升内里。

 

人生当自勉 学习需坚持

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值