Vue 源码初探(三)单组件挂载(渲染)

515 阅读2分钟

思维导图

Vue源码初探.png

前言

上一篇文章里面主要讲了,Vue内部是如何实现将template转换成ats代码的。以及怎样用ast代码生成出render函数的。Vue 源码初探(二)模板编译

这一篇我们主要来讲怎样通过render函数生成vnodevnode渲染成真实dom挂载到页面上,也就是图中截止到mounted的这块,不包含update数据改变的这一部分,就是Vue的初渲染。

无标题.png

正文

调用render生成vnode

lifecycle.js

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

export function mountComponent(vm) {
  // vm._render()  获取到vnode 

  // vm._update(vnode) 把vnode 渲染成真实节点,更新到页面上
  vm._update(vm._render())
}

export function lifeCycleMixin(Vue){
  //给个对象,把对象渲染成真实DOM
  Vue.prototype._update = function(vnode){
    //采用的是先序深度遍历,创建节点。
    const vm = this
    patch(vm.$el, vnode)
  }
}

render.js

import { isObject } from "./utils"
import { createElement, createText } from "./vnode"

export function renderMixin(Vue) {
  Vue.prototype._c = function () {
    const vm = this
    return createElement(vm, ...arguments)
  }

  Vue.prototype._v = function (text) {
    const vm = this
    return createText(vm, text)
  }

  Vue.prototype._s = function (val) {
    if (isObject(val)) return JSON.stringify(val)
    return val
  }

  //根据render 生成vnode对象
  Vue.prototype._render = function () {
    const vm = this
    let { render } = vm.$options

    // 把ast转换成vnode 
    let vnode = render.call(vm)
    return vnode
  }
}

// render 函数的样子

// function render() {
//   with (this) { 
//     return _c('div', { id: "app" }, _v(_s(message))) 
//   }
// }

vnode/index.js

export function createElement(vm, tag, data = {}, ...children) {
  return vnode(vm, tag, data, children, data.key, undefined)
}

export function createText(vm, text) {
  return vnode(vm, undefined, undefined, undefined, undefined, text)
}


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

ast与vnode的区别?

  • ast 是描述语法的, 他并没有用户自己的逻辑,只有语法解析出来的内容
  • vnode 是描述dom结构的 ,可以自己去扩展。

根据vnode生成真实dom

patch.js

// 用vnode生成好的DOM结构 替换(replace)掉原有的el
export function patch(el, vnode){
  //删除老节点,根据vnode创建新节点,替换掉老节点。
  const elm = createElm(vnode)
  const parentNode = el.parentNode
  /*
  插入到老的元素下一个节点的前面
  之后再把老的元素删除掉
  */
  parentNode.insertBefore(elm, el.nextSibling)
  parentNode.removeChild(el)
}

//创建真实DOM节点
function createElm(vnode) {
  let {tag, data, children, text, vm} = vnode
  //元素节点
  if(typeof tag === 'string'){
    //给vnode添加一个新的属性存放真实DOM ,就是为了可以通过vnode找到真实节点并更新。
    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)
  }

  return vnode.el
}

function updateProperties(el, props = {}) {
  for(let key in props){
    el.setAttribute(key, props[key])
  }
}

总结

本小结大家主要明白通过render如何生成vnode,并转换成真实dom渲染到页面上的,这样就完成了最基本的数据到页面的渲染过程。后面一节会继续写当数据发生变化了如何通知到页面让页面自动触发更新页面。也就是图中的虚线环形过程。

系列文章链接(持续更新中...)

Vue 源码初探(一)响应式原理

Vue 源码初探(二)模板编译

Vue 源码初探(三)单组件挂载(渲染)

Vue 源码初探(四)依赖(对象)收集的过程

Vue 源码初探(五)对象异步更新nextTick()