深究mount挂载原理

159 阅读1分钟

深究mount挂载原理

  • 初次挂载
  • 更新

初次挂载

  • 无 el

  • 有 el

image-20231012112623357.png

无 el

等待手动挂载

有 el

  • 有 render

  • 无 render

image-20231012112832334.png

有 render

mount.call(this, el, hydrating);

image-20231012113058739.png

image-20231012133630443.png

调用钩子-beforeMount
callHook$1(vm, 'beforeMount');
渲染
vm._update(vm._render(), hydrating);
_render() 生成虚拟节点

image-20231012133114669.png

_update()生成真实dom

根据_render()生成的虚拟节点VNode生成真实dom

image-20231012133214964.png

调用钩子-mounted
if (vm.$vnode == null) {
  vm._isMounted = true;
  callHook$1(vm, "mounted");
}

无 render

  • 有 template
  • 无 template
  • 生成render函数
  • 调用mount完成挂载
有 template

如果用户提供了template选项,那么需要对它进一步解析。

获取template(模版解析)
  • template 是字符串
  • template是dom
  • 包含nodeType属性

字符串

#开头

如果tempalte是字符串并且以#开头,则它将被用作选择符。通过选择符获取DOM元素后,会使用innerHTML作为模板。

if (typeof template === 'string') {
  if (template.charAt(0) === '#') {
      template = idToTemplate(template);
      /* istanbul ignore if */
      if (!template) {
          warn$2("Template element not found or is empty: ".concat(options.template), this);
      }
  }
}

使用idToTemplate方法从选择符中获取模板。idToTemplate使用选择符获取DOM元素之后,将它的innerHTML作为模板。

var idToTemplate = cached(function (id) {
  var el = query(id);
  return el && el.innerHTML;
});

#开头

如果template是字符串,但不是以#开头,就说明template是用户设置的模板,不需要进行任何处理,直接使用即可。

dom

直接使用即可

nodeType

不是dom,判断nodeType属性是否存在,存在,使用innerHTML作为模板,反之抛出警告。

else if (template.nodeType) {
  template = template.innerHTML;
}
else {
  {
      warn$2('invalid template option:' + template, this);
  }
  return this;
}
无 template

如果用户没有通过template选项设置模板,那么会从el选项中获取HTML字符串当作模板。

else if (el) {
  template = getOuterHTML(el);
}
function getOuterHTML(el) {
  if (el.outerHTML) {
      return el.outerHTML;
  }
  else {
      var container = document.createElement('div');
      container.appendChild(el.cloneNode(true));
      return container.innerHTML;
  }
}
生成render函数

通过执行compileToFunctions函数可以将模板编译成渲染函数并设置到this.options上。

if (template) {
  /* istanbul ignore if */
  if (config.performance && mark) {
      mark('compile');
  }
  var _a = compileToFunctions(template, {
      outputSourceRange: true,
      shouldDecodeNewlines: shouldDecodeNewlines,
      shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
      delimiters: options.delimiters,
      comments: options.comments
  }, this), render = _a.render, staticRenderFns = _a.staticRenderFns;
  options.render = render;
  options.staticRenderFns = staticRenderFns;
  /* istanbul ignore if */
  if (config.performance && mark) {
      mark('compile end');
      measure("vue ".concat(this._name, " compile"), 'compile', 'compile end');
  }
}

image-20231012140358735.png

function createFunction(code, errors) {
  try {
      return new Function(code);
  }
  catch (err) {
      errors.push({ err: err, code: code });
      return noop;
  }
}
调用mount挂载
mount.call(this, el, hydrating);

步骤与有render时一致

更新

在初始化实例时,已经通过initState(vm) 对实例状态做了响应式处理(添加getter和setter)。

function initState(vm) {
  var opts = vm.$options;
  if (opts.props)
      initProps$1(vm, opts.props);
  // Composition API
  initSetup(vm);
  if (opts.methods)
      initMethods(vm, opts.methods);
  if (opts.data) {
      initData(vm);
  }
  else {
      var ob = observe((vm._data = {}));
      ob && ob.vmCount++;
  }
  if (opts.computed)
      initComputed$1(vm, opts.computed);
  if (opts.watch && opts.watch !== nativeWatch) {
      initWatch(vm, opts.watch);
  }
}

当挂载时,会创建一个Watcher类的对象。会执行updateComponent()

updateComponent = function () {
  vm._update(vm._render(), hydrating);
};
new Watcher(vm, updateComponent, noop, watcherOptions, true /* isRenderWatcher */);

当执行_render()时,读取数据则会调用getter方法,并完成依赖收集。

当数据发生变化时,会调用setter方法,并通过notify方法通知watcher调用update,完成更新。