五、vue的dom初始化渲染流程

252 阅读1分钟

1、前言

初始化挂载的实现:根据render方法产生虚拟节点,在将虚拟节点变成真实节点 插入到页面中即可;

Vue.prototype.$mount = function(el){
        const vm = this;
        el  = document.querySelector(el);
        vm.$el = el;
        ...
        mountComponent(vm,el)// 组件挂载流程 
    };
    function mountComponent(vm){
          vm._update(vm._render());
    }

2、调用vm._render() 生成一个虚拟dom

将模板编译的的render函数执行,生成一个虚拟dom ;之前的编译render函数返回的code 类似于这样(_c("div",{id:"app"},[_c("span",{class:"a"},[_v("name")]),_v(_s(name))])

虚拟dom的形式

function vnode(vm, tag, props, children, text, key) {
    return {
        vm,
        tag,
        props,
        children,
        text,
        key
    }
}
2.1、_render函数
 Vue.prototype._render = function(){ // 调用编译后的render方法,生成虚拟节点
        const vm = this;
        let {render} = vm.$options;
        let vnode = render.call(vm); // 这里会进行取值操作 触发了get方法 仅需取值 (依赖收集)
        return vnode
    }

2.2 render函数中_c、_v、_s函数
     // _c 创建标签节点
    Vue.prototype._c = function(){ // _c('div',undefoined,[])
        return createElement(this,...arguments)
    }
      //创建文本节点
    Vue.prototype._v = function(text){ // _v(字符串 + name + 'xxx')
        return createTextElement(this,text)
    }
    //变量
    Vue.prototype._s = function(val){ // _s(name)
        if(typeof val === 'object') return JSON.stringify(val);
        return val;
    }
    //创建节点
    function createElement(vm,tag, props = {}, children) {
        return vnode(vm, tag, props, children,undefined, props.key)
    }
    //创建文本
    function createTextElement(vm,text) {
        return vnode(vm, undefined, undefined, undefined, text)
    }
     //虚拟dom
    function vnode(vm, tag, props, children, text, key) {
        return {
        vm,
        tag,
        props,
        children,
        text,
        key
        }
    }

3、调用_update()方法,实现虚拟dom变成真实dom完成初始化挂载

Vue.prototype._update = function (vnode){ // 虚拟dom变成真实dom进行渲染
        const vm = this;
        const el = vm.$el;   //<div id="app"></div>
        patch(el,vnode); // 渲染  传入一个真实的dom 和 vnode
      
3.1 patch 方法,初始化渲染时,先根据虚拟节点创建一个真实节点,将此创建节点插入到页面中再将老节点删除 ,完成挂载

function patch(oldVnode, vnode) {
    if (oldVnode.nodeType === 1) {  //nodeType dom的原生属性  1 为元素  3为文本
        // 初始化渲染操作
        // 根据虚拟节点创造真实节点, 先根据虚拟节点创建一个真实节点,将节点插入到页面中在将老节点删除 
        const parentElm = oldVnode.parentNode; // 获取父元素
        const elm = createElm(vnode)
        parentElm.insertBefore(elm, oldVnode.nextSibling)
        parentElm.removeChild(oldVnode);

        return elm;
    } else {
      //diff 算法...
    }
}
function createElm(vnode) {
    const { tag, props, children, text } = vnode;
    if (typeof tag == 'string') {
        vnode.el = document.createElement(tag); // 把创建的真实dom和虚拟dom映射在一起方便后续更新复用
        updateProperties(vnode); //解析vnode的属性 
        children && children.forEach(child => {
            vnode.el.appendChild(createElm(child))
        });
    } else {
        vnode.el = document.createTextNode(text);
    }
    return vnode.el;
}



// 解析vnode的data属性
function updateProperties(vnode) {
    let newProps = vnode.props || {}; 
    let newStyle = newProps.style || {};
    let el = vnode.el; //真实节点
    for (let key in newProps) {
    // style需要特殊处理下 
    if (key === "style") { 
        for (let styleName in newStyle) {
            el.style[styleName] = newStyle[styleName]; } 
        } else if (key === "class") { 
            el.className = newProps.class; 
    } else {
        // 给这个元素添加属性 值就是对应的值 
        el.setAttribute(key, newProps[key]); 
    } 
    } 
}