前言
上一篇中我们主要讲的Vue2.0
的模版编译原理,它主要是通过不同的正则表达式对模版进行匹配切割从而生产ast
语法树,然后遍历语法树生成带有_c
、_v
和_s
等字符的code
字符串代码。利用new Function
来生成render
函数。本文在上一篇的基础上,生成虚拟Vnode
并且进行渲染。
我个人推荐的看源码的方式最好是通过视频文章自己能手写一边然后再去看,否则就会造成这样的结果。
真是卷...
正文
挂载入口
javascript
// core/instance/lifecycle.js
export function mountComponent(vm, el) {
// 1. 调通render方法生成虚拟dom
vm.$el = el;
const updateComponent = () => {
vm._update(vm._render()); // vm._render()执行就生成虚拟Vnode
}
new Watcher(vm, updateComponent, true); // 创建渲染Watcher
}
组件调用$mount
进行挂载,其核心就是调用mountComponent
,在render
函数,函数内部类似于_c('div', {id:"app"}, _c('p', {style:{"color":" red"}}, _v("我的姓名是:"+_s(name)+"我的年龄是:"+_s(age))),_c('p', null, _v(_s(maxAge))),_c('myh', null, ),_c('p', null, )),执行的过程中会执行到_c
、_v
等函数,所以我们需要事先定义出来,同时它还执行了update
方法。
javascript
// core/instance/lifecycle.js
export function initLifeCycle(Vue) {
Vue.prototype._update = function (vnode) {
const vm = this;
const el = vm.$el;
const prevVnode = vm._vnode;
vm._vnode = vnode;
if (!prevVnode) { // 之前没有生成过vnode,代表初次渲染
vm.$el = patch(el, vnode);
} else {
/* 这里时更新渲染,后续在讲 */
}
}
Vue.prototype._c = function () { // 标签
return createElement(this, ...arguments);
}
Vue.prototype._v = function () { // 文本节点
return createTextVNode(this, ...arguments);
}
Vue.prototype._s = function (value) { // {{}} 文本
if (typeof value !== 'object') {
return value;
}
return JSON.stringify(value);
}
Vue.prototype._render = function () {
const vm = this;
return vm.$options.render.call(vm);
}
}
因为render
函数是通过一串code
字符串结合Function
和with
来实现的,所以Vue2.0
将函数都定义在了Vue
的原型上。
生成虚拟Vnode
javascript
// core/vdom/create-element.js
// vm Vue当前实例
// tag 标签名
// data 属性
// children 子元素
export function createElement(vm, tag, data = {}, ...children) {
if (!data) {
data = {};
}
let key = data.key;
if (key) {
delete data.key;
}
return vnode(vm, tag, key, data, children);
}
// 创建文本虚拟dom
export function createTextVNode(vm, text) {
return vnode(vm, undefined, undefined, undefined, undefined, text);
}
arduino
// core/vdom/vnode.js
// 创建虚拟dom
export function vnode(vm, tag, key, data = {}, children, text, componentOptions) {
return {
vm,
tag,
key,
data,
children,
text,
componentOptions // 组件构造函数
}
}
上文主要将通过createElement
和createTextVNode
创建虚拟Vnode
,Vnode
包含了tag
标签名、key
、属性data
以及children
子元素。
虚拟dom转为真实dom
ini
// core/instance/lifecycle.js
Vue.prototype._update = function (vnode) {
const vm = this;
const el = vm.$el;
const prevVnode = vm._vnode; // 获取老的vnode
vm._vnode = vnode;
if (!prevVnode) { // 如果老vnode不存在,那就初始化渲染
vm.$el = patch(el, vnode);
} else { // 更新渲染
vm.$el = patch(prevVnode, vnode);
}
}
scss
export function patch(oldVNode, vnode) {
if(!oldVNode) {
return createElm(vnode);
}
const isRealElement = oldVNode.nodeType; // 如果存在nodeType属性,那传入的就是真实dom
if (isRealElement) { // 第一次渲染
const elm = oldVNode;
const parentElm = elm.parentNode; // 拿到父元素
let newElm = createElm(vnode); // 将vnode 转为真实dom
parentElm.insertBefore(newElm, elm.nextSibling); // 将真实dom插入到页面
parentElm.removeChild(elm);
return newElm;
} else { // 更新渲染
patchVnode(oldVNode, vnode);
return vnode.el;
}
}
update
函数只要就是将vnode
转为真实的dom
,并渲染到页面上。在我们第一次生成vnode
时,会将vnode
赋值在实例的_vnode
上,所以第一次渲染时拿不到_vnode
的,这就是判断初始化渲染和更新渲染的依据。在初始化渲染时调用patch
,第一个参数传入的时真实el
,直接通过createElm
将vnode
转为真实dom
插入到el
之前,并删除el
。
思维导图
总结
Vue
初始化渲染原理已完结,主要使用过模板编译生成render
函数,创建渲染Watcher
来进行初次渲染。在渲染的过程中响应式数据会对当前渲染Watcher
进行依赖收集。渲染主要通过update
函数来判断是否时初次渲染,初次渲染直接将vnode
转为真实dom
插入到目标位置。
如果觉得本文有帮助 记得点赞三连哦 十分感谢!