亲宝软件园·资讯

展开

Vue组件初渲染

夏日 人气:0

前言

Vue进行文本编译之后,会得到代码字符串生成的render函数。本文会基于render函数介绍以下内容:

vm.$mount方法中,文本编译完成后,要进行组件的挂载,代码如下:

Vue.prototype.$mount = function (el) {
  // text compile code ....
  mountComponent(vm);
};

// src/lifecycle.js
export function mountComponent (vm) {
  vm._update(vm._render());
}

下面详细介绍vm._render()vm._update()中到底做了什么

生成虚拟节点

原生DOM节点拥有大量的属性和方法,操作DOM比较耗费性能。在Vue中通过一个对象来描述DOM中的节点,这个对象就是虚拟节点,Vue组件树构建的整个虚拟节点树就是虚拟DOM

这是一段html

<div id="app">
  <span>hello world {{name}}</span>
</div>
<script>
  new Vue({
    el: '#app',
    data () {
      return {
        name: 'zs'
      }
    }
  })
</script>

其对应的虚拟节点如下:

const vNode = {
  tag: 'div',
  props: { id: 'app' },
  key: undefined,
  children: [
    {
      tag: 'span',
      props: {},
      key: undefined,
      children: undefined,
      text: 'helloworldzs'
    }
  ],
  text: undefined
}

Vue.prototype._render函数中,通过执行文本编译后生成的render方法,会得到虚拟节点:

// src/vdom/index.js
Vue.prototype._render = function () {
  const vm = this;
  // 执行选项中的render方法,指定this为Vue实例
  const { render } = vm.$options;
  return render.call(vm);
};

render函数中用到了_c,_v,_s这些方法,需要在Vue.prototype上添加这些方法,在render函数内就可以通过实例调用它们:

// 创建虚拟节点
function vNode (tag, props, key, children, text) {
  return {
    tag,
    props,
    key,
    children,
    text
  };
}

// 创建虚拟元素节点
function createVElement (tag, props = {}, ...children) {
  const { key } = props;
  delete props.key;
  return vNode(tag, props, key, children);
}

// 创建虚拟文本节点
function createTextVNode (text) {
  return vNode(undefined, undefined, undefined, undefined, text);
}

// 将实例中data里的值转换为字符串
function stringify (value) {
  if (value == null) {
    return '';
  } else if (typeof value === 'object') {
    return JSON.stringify(value);
  } else {
    return value;
  }
}

export function renderMixin (Vue) {
  Vue.prototype._c = createVElement;
  Vue.prototype._v = createTextVNode;
  Vue.prototype._s = stringify;
  // some code ...  
}

_render函数最终会递归的调用这些函数来得到虚拟节点,并将其返回:

const vNode = vm.createVElement('div', { id: 'app' },
  vm.createVElement('span', undefined,
    vm.createTextVNode('hello') + vm.createTextVNode('world') + vm.stringify(vm.name)
  )
)

在生成虚拟节点的过程中,会从组件实例vm中取值,从而触发对应属性的get/set方法。

将虚拟节点处理为真实节点

在通过Vue.prototype._render函数生成虚拟节点后,在Vue.prototype._update方法中会利用虚拟节点,替换当前页面上渲染的元素app

其代码如下:

// src/lifecycle.js
export function lifecycleMixin (Vue) {
  Vue.prototype._update = function (vNode) {
    const vm = this;
    patch(vm.$el, vNode);
  };
}

patch方法中,会通过虚拟节点创建真实节点,并将真实节点插入页面中:

// src/vdom/patch.js
export function patch (oldVNode, vNode) {
  // 将虚拟节点创建为真实节点,并插入到dom中
  const el = createElement(vNode);
  // 获取到老节点的父节点
  const parentNode = oldVNode.parentNode;
  // 将新节点插入到老节点之后
  parentNode.insertBefore(el, oldVNode.nextSibling);
  // 删除老节点
  parentNode.removeChild(oldVNode);
}

createElement中是用虚拟节点生成真实节点的逻辑:

function createElement (vNode) {
  if (typeof vNode.tag === 'string') {
    vNode.el = document.createElement(vNode.tag);
    updateProperties(vNode);
    for (let i = 0; i < vNode.children.length; i++) {
      const child = vNode.children[i];
      vNode.el.appendChild(createElement(child));
    }
  } else {
    vNode.el = document.createTextNode(vNode.text);
  }
  return vNode.el;
}

createElement会生成的真实DOM元素el并返回,内部会对子虚拟节点再次调用createElement来继续生成真实元素,然后将生成的真实元素通过appendChild方法插入到父节点中。

执行createElement最后得到的el是将所有子节点都插入到内部的元素,但其实el此时还是脱离真实DOM存在的,最后将它插入到真实DOM中便完成了整个真实节点的渲染。

下面是其执行逻辑示意图:

总结

Vue的组件挂载vm.$mount(el)过程如下:

到目前为止,我们已经实现了Vue组件初渲染的整个过程,下面用一张图来总结一下:

源码地址: 传送门

加载全部内容

相关教程
猜你喜欢
用户评论