前言
- 文分【思路篇】和【实现篇】,本文为实现篇第一版,文末有第二版链接,建议看两个窗口同步阅读,或请先阅读-》Vue|思路篇|实现ast
实现template写法兼容
初始化时判断有无el属性,有则执行$mount方法(以实现用户自控制挂载时机)
init.js
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = options;
// 初始化数据
initState(vm)
if(vm.$options.el){
vm.$mount(vm.$options.el);
}
}
$mount中先判断用户有无定义render,如定义直接使用此函数
无定义则判断有无template,如果无则根据el获取html字符串赋值给template,根据template通过compilerToFunction生成render函数
Vue.prototype.$mount = function (el){
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
if(!options.render){
// 如果无render属性 判断有无template
let template = options.template;
// 无template 且有el则将el内容赋值给template
if(!template && el){
template = el.outerHTML;
}
const render = compilerToFunction(template);
options.render = render;
}
}
实现render方法
定义vdom结构{tag,data,key,children,text }及根据信息创建vdom的函数
vdom/index.js
function createElement(tag,data={},...children) {
let key = data.key;
if(key){
delete data.key;
}
return vnode(tag,data,key,children);
}
function createTextVnode(text) {
return vnode(undefined,undefined,undefined,undefined,text)
}
// 产生虚拟dom 与ast相比 差异在于 可以自定义数据格式
function vnode(tag,data,key,children,text) {
return {
tag,data,key,children,text
}
}
将_c传递给render,然后获取render函数返回值,是一个vdom
vdom/index.js
Vue.prototype._c = function () { // 创建元素
return createElement(...arguments);
}
通过vdom渲染页面可以采用深度遍历+处理dom的方式
- 通过vnode生成对应html结构,注意处理属性
- 通过el找到挂载点,替换即可
Vue.prototype._update = function (vnode) {
console.log("========",vnode);
const vm = this;
patch(vm.$el,vnode)
}
vdom/patch.js
根据vnode渲染页面入口函数
function patch(oldVnode,vnode){
// 初次渲染时 oldVnode 是 el对应的真实dom
const isRealElement = oldVnode.nodeType;
if(isRealElement){
const oldElm = oldVnode;
const parentElm = oldElm.parentNode;
// 通过vnode生成对应html结构
let el = createElm(vnode);
// 替换掉挂载点
parentElm.insertBefore(el,oldElm.nextSibling);
parentElm.removeChild(oldVnode)
return el;
}
}
根据vnode创建真实dom
function createElm(vnode){
let {tag,children,key,data,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]);
}
}
}