vue2源码---初始化渲染

99 阅读1分钟

1、第一步根据生成的render函数生成虚拟节点

1.1 创建元素节点

export function createElement (vm,tag,data = {},...children) { // 返回虚拟节点  标签
  return vnode(vm,tag,data,children,data.key,undefined)
}



function vnode(vm,tag,data,children,key,text) {
  return {
    vm,
    tag,
    data,
    children,
    key,
    text
  }
}

1.2 创建文本节点

export function createText (vm,val) { // 文本节点
   return vnode(vm,undefined,undefined,undefined,undefined,val)
}
function vnode(vm,tag,data,children,key,text) {
  return {
    vm,
    tag,
    data,
    children,
    key,
    text
  }
}
import { isObject } from "./util"
import { createElement, createText } from "./vdom"

export function renderMixins(Vue) {
  Vue.prototype._c = function () { // createElement 创建元素型节点
    const vm = this;
    return createElement(vm,...arguments)
  }
  Vue.prototype._v = function(text) {
    const vm = this;
    return createText(vm,text)
  }
  Vue.prototype._s = function(val) {
    // console.log('s',arguments)
    if (isObject(val)) return JSON.stringify(val)
    return val
  }
  Vue.prototype._render = function () {
     const vm = this
     const { render } = vm.$options
    //  console.log(render.toString(),vm)
     let vnode = render.call(vm)
    //  console.log(vnode)
    return vnode
  }
}

2、第二步 根据虚拟节点创建真实的节点 替换原来的节点

// 将虚拟节点变成真实节点
   export function patch(el,vnode) {
     // 删除老节点 根据vnode创建新节点 替换掉老节点
     const elm = createElm(vnode) // 根据虚拟节点创建真实节点
     const parentNode = el.parentNode
     parentNode.insertBefore(elm,el.nextSibling) // el.nextSibling不存在就是null 如果为null insertBefore就是appendChild
     parentNode.removeChild(el)
     return elm
   }
  
   function createElm (vnode) {
     let { tag, data, children,text} = vnode
     if (typeof tag === 'string') { // 根据tag的类型判断是否是文本节点
       vnode.el = document.createElement(tag) // 创建节点
       // 如果data有属性 我们需要将data上的属性设置到元素上
       updateProperties(vnode.el,data)
       children.forEach(child => {
        vnode.el.appendChild(createElm(child))
       })
     } else {
      vnode.el = document.createTextNode(text) // 创建文本节点
     }
    //  console.log('el',vnode)
     return vnode.el
   }
 function updateProperties(el,props = {}) {
  for (let key in props) {
    if (key === 'style') {
      for (let styleName in props[key]) {
        el.style[styleName] = props[key][styleName]
      }
    } else if (key === 'class') {
      el.className = props[key]
    } else {
      el.setAttribute(key,props[key])
    }
  }
 }

3、将真实节点挂载到$el上 更新视图

import { patch } from "./vdom/patch"

 export function  mountComponent(vm) {
  let updateComponent = () => {
    vm._update(vm._render())
  } 
  updateComponent()
 }

 export function lifeCycleMixin(Vue) {
   Vue.prototype._update = function (vnode) {
     // 采用的是 先序深度遍历 创建节点 (遇到节点就创造节点 递归创建)
     const vm = this 
     vm.$el = patch(vm.$el,vnode)
   }
 }

通过$mount()挂载

import { compileToFunction } from "./compiler"
import { mountComponent } from "./liftcycle"

export function initMixins (Vue) {
  Vue.prototype._init = function (options) {
    const vm = this
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
  Vue.prototype.$mount = function (el) {
    const vm = this
    const opts = vm.$options
    el = document.querySelector(el) // 获取真实的元素
    vm.$el = el // 页面真实的元素
    if (!opts.render) {
      // 模板编译
      let template = opts.template
      if (!template) {
        template = el.outerHTML
      }
      let render = compileToFunction(template)
      opts.render = render
    }
    // 这里已经获取到了一个render函数 
    mountComponent(vm)
  }
}