Vue原理:初次渲染流程和创建虚拟DOM

这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战

Vue初次渲染核心流程大致为:解析模版生成render函数、调用render函数生成虚拟DOM、通过虚拟DOM创建真实DOM渲染至浏览器展示

核心流程

前文已经完成了render函数生成,接下来便是挂载的流程,首先修改$mount函数,增加mountComponent函数作为挂载的入口

/**
 * 渲染流程
 */
Vue.prototype.$mount = function (_el) {
  const vm = this;
  const ops = this.$options;
  const el = document.querySelector(_el);

  if (!ops.render) {
    // 不存在render函数, 需要将其进行编译
    let template = ops.template;

    if (!template && el) {
      // 没有传递模版 且传递了el
      template = el.outerHTML; // 将el的内容作为模版
    }

    // 将template 转换为 render
    const { render } = compileToFunction(template);
    // 
    ops.render = render;
  }
  
  // 挂载组件
  mountComponent(vm, el);
};
复制代码

初次渲染流程属于Vue生命周期内需要做的事情,将mountComponent函数抽取到lifecycle.js中,初始化函数

export function mountComponent(vm, el) {
  const options = vm.$options;
  vm.$el = el; // 真实的DOM元素
  // todo ...
}
复制代码

在这个函数中主要做的事情有两个

  1. 调用render函数生成虚拟DOM
  2. 通过虚拟DOM创建真实DOM渲染

_render

需要注意这里的_render内部调用的就是前文的render函数

Vue.prototype._render = function () {
  const vm = this;
  const { render } = vm.$options;
  
  // render 去实例上取值, 返回 vnode
  return render.call(vm);
};
复制代码

_update

通过调用_render返回虚拟DOM,创建真实dom并渲染,重要的操作提取至patch函数中

Vue.prototype._update = function (vnode) {
  const vm = this;

  // 需要用虚拟节点创建出来真实节点 替换掉 真实的 $el
  vm.$el = patch(vm.$el, vnode);
};
复制代码

至此可得到mountComponent核心工作

vm._update(vm._render());
复制代码

render

在调用render时,返回的数据结构

function render() {
  with(this) {
    return _c('div', {
      attrs: {
        "id": "app"
      }
    }, [_c('p', [_v("hi " + _s(msg))]), _v(" hello")])
  }
}
复制代码

其中的这些_c_v等函数,返回的便是虚拟DOM,接下来便看看这些函数的具体实现

_c_v_s等函数的实现封装src/render.js中,通过在src/index.js中引用并执行

index.js新增两行代码

+++ import { renderMixin } from "./render"; // 引用
+++ renderMixin(Vue); // 执行
复制代码

render.js中大体结构

export function renderMixin(Vue) {
  /**
   * _c 创建元素的虚拟节点
   * _v 创建文本的虚拟节点
   * _s JSON.stringify
   */

  Vue.prototype._c = function () {};

  Vue.prototype._v = function (text) {};

  Vue.prototype._s = function (val) {};
}
复制代码

创建虚拟DOM

_c_v就是用来创建我们常说的虚拟节点,首先看一下创建虚拟DOM的方法vnode

const vnode = (tag, data, key, children, text) => {
  return {
    tag,
    data,
    key,
    children,
    text,
  };
};
复制代码

_c:用来创建元素的虚拟节点,其内部是根据createElement进行创建

/**
 * 创建元素的虚拟节点
 */
const createElement = (tag, data, ...children) => {
  let key = data && data.key;

  if (key) {
    delete data.key;
  }

  return vnode(tag, data, key, children, undefined);
}

const _c = function () {
  return createElement(...arguments);
};
复制代码

_v:用来创建文本的虚拟节点,其内部是根据createTextNode进行创建

/**
 * 创建文本的虚拟节点
 */
const createTextNode = (text) => {
  return vnode(undefined, undefined, undefined, undefined, text);
}

const _v = function (text) {
  return createTextNode(text);
};
复制代码

_s:其做的事情比较简单,进行字符串序列化

const _s =  (val) => {
  return val === null ? "" : typeof val === "object" ? JSON.stringify(val): val;
};
复制代码

Vue中拥有很多此类的方法,本文不做更多的解释,具体可以参考源码src/core/instance/render-helpers/index.js

export function installRenderHelpers (target) {
  target._o = markOnce
  target._n = toNumber
  target._s = toString
  target._l = renderList
  target._t = renderSlot
  target._q = looseEqual
  target._i = looseIndexOf
  target._m = renderStatic
  target._f = resolveFilter
  target._k = checkKeyCodes
  target._b = bindObjectProps
  target._v = createTextVNode
  target._e = createEmptyVNode
  target._u = resolveScopedSlots
  target._g = bindObjectListeners
  target._d = bindDynamicKeys
  target._p = prependModifier
}
复制代码

至此Vue的初次渲染的大致架子已经完成,也对其创建虚拟DOM的几个常用函数做了介绍,接下来就需要看看是如何通过虚拟DOM创建真实DOM并渲染至浏览器中

猜你喜欢

转载自juejin.im/post/7033584491419402276