【Vue源码初探】三.初次渲染视图

三.初次渲染视图


现在,我们已经实现了render函数,将数据渲染到页面上的操作已经进行了一半了(我们实现了将数据转成ast,再转成render函数),接下来就是将render函数进行调用,挂载到页面上。

export function initMixin(Vue) {
    
    
  Vue.prototype._init = function(options){
    
     
  	//...
  }
  
  Vue.prototype.$mount = function(el) {
    
    
  	//...
  	
  	mountComponent(vm, el); //组件的挂载
  }
}

下面我们就开始实现这个**mountComponent(vm, el)**函数。

export function initlifeCycle() {
    
    
    Vue.prototype._update = function (vnode) {
    
    }
  	Vue.prototype._render = function (vnode) {
    
    }
}

export function mountComponent(vm, el) {
    
    
    vm.$el = el;
  
  // 1.调用render方法产生虚拟节点 虚拟DOM
  // vm._render()

  // 2.根据虚拟DOM产生真实DOM
  // vm._update(vm._render())
  
    let updateComponent = () => {
    
    
        // 将虚拟节点 渲染到页面上
        vm._update(vm._render());
    }

}

步骤

  • 调用render方法产生虚拟节点 虚拟DOM -> vm._render() 返回虚拟DOM
  • 根据虚拟DOM产生真实DOM -> vm._update() 虚拟节点产生真实DOM
  • 插入到el元素中

一.调用render方法产生虚拟DOM

vm._render()

当调用_render方法时,其实我们应该调用原来我们自己生成的render方法。

并且因为我们的自定义render方法中出现了: _c, _v, _s 那么我们也需要实现这些方法。

export function initLifeCycle(Vue){
    
    
  Vue.prototype._update = function(vnode){
    
     //将vnode转换成真实dom
    //...
  }

  Vue.prototype._v = function () {
    
     // 创建文本
    return createTextVNode(this,...arguments);
  }
  Vue.prototype._c = function () {
    
     // 创建元素
    return createElementVNode(this,...arguments);
  }
  Vue.prototype._s = function (val) {
    
    
    if(typeof val !== 'object') return val
    return JSON.stringify(val);
  }
  Vue.prototype._render = function(){
    
    
    const vm = this;
    // call: 让with中的this指向vm
    return vm.$options.render.call(vm); //执行render函数拿到虚拟节点
  }
}

上面提到的createTextVNode,createElementVNode函数我们都定义在另一个js文件中

//_c() 创建文本
export function createTextVNode(vm,text){
    
    
  return vnode(vm,undefined,undefined,undefined,undefined,text)
}

//_v() 创建节点
export function createElementVNode(vm, tag, data = {
     
     }, ...children){
    
    
  if(data == null){
    
    
    data = {
    
    }
  }
  let key = data.key;
  if(key){
    
     //上面我们已经取出了key,所以可以删掉key
    delete data.key
  }
  return vnode(vm,tag,key,data,children)
}

// 虚拟dom比ast功能更强大,可以增加一些自定义属性
function vnode(vm,tag,key,data,children,text){
    
    
  return {
    
    
    vm,
    tag,
    key,
    data,
    children,
    text
  }
}

export function isSameVNode(vnode1, vnode2){
    
    
  return vnode1.tag === vnode2.tag && vnode1.key === vnode2.key
}

此时我们就已经产生了虚拟DOM


二.虚拟DOM产生真实DOM + 绑定到页面上

vm._update()

我们会实现一个patch方法,来将虚拟DOM转成真实DOM。

注意,patch方法既有初始化的功能,又有更新的功能。以后实现diff算法也是靠这个patch函数。

Vue.prototype._update = function(vnode){
    
     //将vnode转换成真实dom
    const vm = this;
    const el = vm.$el;

    //patch既有初始化的功能,又有更新的功能
    vm.$el = patch(el,vnode); //将VNode虚拟节点挂载到el元素上

  }

实现patch方法:

// patch(el,vnode)
export function patch(oldVNode, vnode){
    
    
  const isRealElement = oldVNode.nodeType;
  if(isRealElement){
    
      //初渲染流程
    const elm = oldVNode; //获取真实元素
    const parentElm = elm.parentNode; //拿到父元素
    let newElm = createElm(vnode); //这里就将虚拟DOM转成真实DOM: createElm函数
    //此时元素已经创建好了,属性也绑定了
    parentElm.insertBefore(newElm, elm.nextSibling) //插入新节点
    parentElm.removeChild(elm) //删除老节点

    return newElm
  }else{
    
    
    //diff算法  
  }
}

//将虚拟DOM转成真实DOM:创建真实元素
export function createElm(vnode){
    
    
  let {
    
     tag, data, children, text } = vnode;
  if(typeof tag === 'string'){
    
     //如果是标签
    vnode.el = document.createElement(tag); //??? 将真实节点和虚拟节点对应起来
    patchProps(vnode.el, {
    
    }, data) //绑定属性
    children.forEach(child => {
    
    
      vnode.el.appendChild(createElm(child))
    });
  } else{
    
     //如果是文本
    vnode.el = document.createTextNode(text)
  }
  return vnode.el
}

//绑定属性
function patchProps(el, oldProps = {
     
     }, props = {
     
     }){
    
    
    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]);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_52834435/article/details/127261086