前言
根据vue官网所说,虽然是vue3文档的说法,但我的理解是大同小异:
书接上文,vue组件在初始化渲染时经过了模板编译生成了render函数,那么按照官方所说,就需要再这个时候渲染器调用渲染函数,遍历返回虚拟Dom树。
1.组件挂载入口
我们去找$mount看看,也就是vue在渲染挂载时做了什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
import { initState } from "./state"; import { compileToFunctions } from "./compiler/index"; export function initMixin(Vue) { Vue.prototype._init = function (options) { const vm = this; vm.$options = options; initState(vm);
if (vm.$options.el) { vm.$mount(vm.$options.el); } };
Vue.prototype.$mount = function (el) { const vm = this; const options = vm.$options; el = document.querySelector(el);
if (!options.render) { let template = options.template;
if (!template && el) { template = el.outerHTML; }
if (template) { const render = compileToFunctions(template); options.render = render; } } return mountComponent(vm, el); }; }
|
2. mountComponent函数(渲染的入口函数)
可以看出当生成完了render函数后,开始调用mountComponent方法
1 2 3 4 5 6 7 8 9 10 11
| export function mountComponent(vm, el) {
vm.$el = el; vm._update(vm._render()); }
|
3. _render函数(执行render函数,获得虚拟DOM)
通过_render将render函数转化为虚拟dom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
import { createElement, createTextNode } from "./vdom/index";
export function renderMixin(Vue) { Vue.prototype._render = function () { const vm = this; const { render } = vm.$options; const vnode = render.call(vm); return vnode; };
Vue.prototype._c = function (...args) { return createElement(...args); };
Vue.prototype._v = function (text) { return createTextNode(text); }; Vue.prototype._s = function (val) { return val == null ? "" : typeof val === "object" ? JSON.stringify(val) : val; }; }
|
虚拟 dom相关功能,定义 Vnode 类 以及 createElement 和 createTextNode 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
export default class Vnode { constructor(tag, data, key, children, text) { this.tag = tag; this.data = data; this.key = key; this.children = children; this.text = text; } }
export function createElement(tag, data = {}, ...children) { let key = data.key; return new Vnode(tag, data, key, children); }
export function createTextNode(text) { return new Vnode(undefined, undefined, undefined, undefined, text); }
|
经过上面_render方法一个个遍历把render函数渲染成虚拟dom,接下来就是通过_update把虚拟dom渲染成真实dom了
1 2 3 4 5 6 7 8 9 10 11
|
import { patch } from "./vdom/patch"; export function lifecycleMixin(Vue) { Vue.prototype._update = function (vnode) { const vm = this; patch(vm.$el, vnode); }; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
|
export function patch(oldVnode, vnode) { const isRealElement = oldVnode.nodeType; if (isRealElement) { const oldElm = oldVnode; const parentElm = oldElm.parentNode; let el = createElm(vnode); parentElm.insertBefore(el, oldElm.nextSibling); parentElm.removeChild(oldVnode); return el; } }
function createElm(vnode) { let { tag, data, key, children, text } = vnode; if (typeof tag === "string") { vnode.el = document.createElement(tag); updateProperties(vnode); children.forEach((child) => { return vnode.el.appendChild(createElm(child)); }); } else { vnode.el = document.createTextNode(text); } return vnode.el; }
function updateProperties(vnode) { let newProps = vnode.data || {}; let el = vnode.el; for (let key in newProps) { if (key === "style") { for (let styleName in newProps.style) { el.style[styleName] = newProps.style[styleName]; } } else if (key === "class") { el.className = newProps.class; } else { el.setAttribute(key, newProps[key]); } } }
|
流程图
参考文章
「Vue源码学习(三)」你不知道的-初次渲染原理 - 掘金 (juejin.cn)