深究mount挂载原理
- 初次挂载
- 更新
初次挂载
-
无 el
-
有 el
无 el
等待手动挂载
有 el
-
有 render
-
无 render
有 render
mount.call(this, el, hydrating);
调用钩子-beforeMount
callHook$1(vm, 'beforeMount');
渲染
vm._update(vm._render(), hydrating);
_render() 生成虚拟节点
_update()生成真实dom
根据_render()生成的虚拟节点VNode生成真实dom
调用钩子-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');
}
}
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,完成更新。