Vue-ast|实现篇第一版|实现render函数

2,254 阅读2分钟

前言

  • 文分【思路篇】和【实现篇】,本文为实现篇第一版,文末有第二版链接,建议看两个窗口同步阅读,或请先阅读-》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]);
        }
    }
}

相关链接