Appearance
四、初渲染原理
- 先初始化数据,进行观测
- 将模板进行编译为render函数
- 利用render.call(vm)进行改变this指向,最终产生虚拟DOM
- 调用_update()方法,执行patch函数将虚拟DOM转化为真实DOM
- 将真实DOM放到页面替换#app
1、在Vue的原型上混入_render和_update方法
js
// src\index.js
import { lifecycleMixin } from "./lifecycle";
import { renderMixin } from "./vdom/index";
// Vue构造函数
function Vue(options) {
// console.log(options);
// 入口方法,做初始化操作
this._init(options)
}
// 混合生命周期和渲染函数
lifecycleMixin(Vue);
// 混入_render方法
renderMixin(Vue);2、生成虚拟DOM
- 在Vue的原型上定义_render方法
- 在Vue的原型上定义很多编译方法_c、_s、_v等
- 执行render.call(vm)的时候,传入vue实例,改变this指向
- 利用with的原理,最后将包括字符串的函数转化为虚拟dom
js
// src\vdom\index.js
export function renderMixin(Vue) {
// 创建虚拟dom-标签元素
Vue.prototype._c = function() {
return createElement(...arguments);
}
// 处理虚拟dom中双大括号{{}}。如果结果一个对象时,stringify会对这个对象取值
Vue.prototype._s = function(val) {
return val == null ? '' : (typeof val == 'object') ? JSON.stringify(val) : val;
}
// 创建虚拟dom-文本元素
Vue.prototype._v = function(text) {
return createTextVnode(text);
}
// 扩展_render方法
Vue.prototype._render = function() { //_render = render
const vm = this;
const render = vm.$options.render;
// 执行render方法,改变里面this的指向为vm,最后生成虚拟dom
let vnode = render.call(vm);
// 返回虚拟dom
return vnode;
}
}
// 生成元素节点的虚拟dom对象
function createElement(tag, data = {}, ...children) {
return vnode(tag, data, data.key, children);
}
// 生成文本节点的虚拟dom对象
function createTextVnode(text) {
return vnode(undefined, undefined, undefined, undefined, text);
}
// 用来产生虚拟dom的,可以自定义一些属性
function vnode(tag, data, key, children, text) {
return {
tag,
data,
key,
children,
text
}
}3、调用渲染函数
js
// src\init.js
// 挂载函数
Vue.prototype.$mount = function(el) {
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
......
// 调用渲染函数
mountComponent(vm, el);
}js
// src\lifecycle.js
import { patch } from "./vdom/patch";
// 定义_update方法
export function lifecycleMixin(Vue) {
Vue.prototype._update = function(vnode) {
const vm = this;
// 将虚拟节点渲染成真实节点:用新创建的元素,替换老的vm.$el
vm.$el = patch(vm.$el, vnode);
}
}
// 定义渲染函数
export function mountComponent(vm, el) {
vm.$el = el;
// 先调用render方法创建虚拟节点,再将虚拟节点渲染到页面上
vm._update(vm._render());
}4、生成真实DOM
js
// src\vdom\patch.js
// 将虚拟节点转化为真实节点
export function patch(oldVnode, vnode) {
// 产生真实的dom
let el = createElm(vnode);
// 获取老的app的父亲-body
let parentElm = oldVnode.parentNode;
// 当前真实元素插入到app的后面
parentElm.insertBefore(el, oldVnode.nextSibling);
// 删除老的节点
parentElm.removeChild(oldVnode);
// 返回新节点进行实时替换
return el;
}
function createElm(vnode) {
let { tag, children, key, data, text } = vnode;
if (typeof tag == 'string') {
// 创建元素,放到vnode.el上
vnode.el = document.createElement(tag);
// 只有元素才有属性
updateProperties(vnode);
// 遍历儿子,将儿子渲染后的结果放到父亲中
children.forEach(child => {
vnode.el.appendChild(createElm(child));
})
} else {
// 创建文本,放到vnode.el上
vnode.el = document.createTextNode(text);
}
return vnode.el;
}
// 处理属性
function updateProperties(vnode) {
let el = vnode.el; // 当前的真实节点
let newProps = vnode.data || {}; // 获取当前节点的属性
for (let key in newProps) {
if (key == 'style') { // {color: red}
// 样式需要遍历添加
for (let styleName in newProps.style) {
el.style[styleName] = newProps.style[styleName];
}
} else if (key == 'class') {
// class直接添加
el.className = el.class;
} else {
// 属性就需要利用方法添加,值就是对应的值
el.setAttribute(key, newProps[key]);
}
}
}