# vue源码 挂载和渲染

template

compiled into => render function
retures => virtual dom
generated => actual dom

# base

# runtime only

  • 自己创建vue实例,手写render函数也可以执行
new vue({
    render(h){
        return h('div',this.hi)
    }
})
  • 要借助vue-loader进行编译,将编译交给插件去实现

# runtime + compiler

  • 可以用个模版,通过挂载,是包含编译器的版本
new vue ({
    template:'{{yiki}}'
})

# virtual dom

  • 虚拟dom 就是将页面抽象为js对象,本质作为js和真实dom的中间层
  • diff
    [object htmldiveele] - brower native obj
    
    { tag : 'div',  data: { attrs: {} , ...}, children: []}
    ^ plain js obj
    

# vnode

  • /core/vnode.js
constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
      ...
  }
  • createEmptyVNode 创建空vnode
  • createTextVNode
  • /src/core/vdom包下有各种相关

# 模版到真实节点的流程

  • 解析:html模版解析为ast树
  • ast树生成可执行的render函数
  • 生成渲染函数render函数,转换为vnode对象
  • 生成真的节点

# 挂载

  • 什么是挂载
    • 传递template为属性的模版字符串,通过中间过程转为真实的dom节点,并挂载到el代表的根结点上完成渲染
  • /core/instance/init.js
... // 一系列别的初始化,开始mount挂载
 if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
...
  • /src/platforms/rumtime/index.js
// public mount method 真正的挂载方法
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
  • mountComponent /src/core/instance/lifecycle.js
  • 注意这里挂载前会创建一个watcher
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
    ...
     updateComponent = () => {
      // render函数渲染出虚拟dom,虚拟dom渲染成真实dom
      vm._update(vm._render(), hydrating)
    }
    ...
    new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
  • /src/platforms/entry-runtime-with-compiler.js
// 重新定义$mount
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
    // 内部编译器编译模版
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
   ...
  }
  // 最终调用
  return mount.call(this, el, hydrating)
}
...
  • template 会转为语法树ast
  • compileToFunctions
 const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,// 是否改变纯文本插入分隔符
        comments: options.comments // html注释
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

# vm._render

  • 回到mountComponent /src/core/instance/lifecycle.js
 updateComponent = () => {
      // render函数渲染出虚拟dom,虚拟dom渲染成真实dom
      vm._update(vm._render(), hydrating)
    }
  • vm._render 是执行render渲染,生成虚拟dom
  • _render /core/instance/render.js
export function renderMixin (Vue: Class<Component>) {
    ...
  Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options
    ...
    let vnode
    try {
      currentRenderingInstance = vm
      // 数据代理过滤 + call(obj,this) 的this
      vnode = render.call(vm._renderProxy, vm.$createElement)
    // 用于 render:()=>{return createelement('div',this.message)}
    } catch (e) {
      ...
    } finally {
      currentRenderingInstance = null
    }
   ...
    return vnode
  }
}
  • $createElement in render.js
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

# vm.update

  • vm._update 转为真是dom节点
  • _update in lifecycleMixin
export function lifecycleMixin (Vue: Class<Component>) {
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // based on the rendering backend used.
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    
  }
  • vm.__patch__ 是浏览器环境下 pathc函数的引用
    • vm.__patch__内部会通过 createElm去创建真实的DOM元素,期间遇到子Vnode会递归调用createElm方法。
    • vnode是一个不断创建子节点的过程
Last Updated: 2022/6/26 上午11:43:14