浅谈Vue.$mount()使用以及原理
一、作用Vue 的$mount()为手动挂载,在项目中可用于延时挂载(例如在挂载之前要进行一些其他操作、判断等),之后要手动挂载上。new Vue时,el和$mount并没有本质上的不同。具体代码如下:new Vue({store,router}).$mount('#app')vue渲染机制流程图:二、原理1.$mount函数的流程:首先,提取出el所对应的...
一、作用
Vue 的$mount()
为手动挂载,在项目中可用于延时挂载(例如在挂载之前要进行一些其他操作、判断等),之后要手动挂载上。new Vue时,el和$mount并没有本质上的不同。
具体代码如下:
new Vue({
store,
router
}).$mount('#app')
vue渲染机制流程图:
二、原理
1.$mount函数的流程:
首先,提取出el所对应的dom元素。主要是query函数。
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
下面讲解**query()**函数。
在vue/src/platforms/web/util/index.js
中,这个函数主要用途是使用原生的querySelector(查找第一个匹配的DOM元素)来查找dom,如果没有找到则新建一个div返回;若el自身就是一个dom元素,直接返回。
但是,el不能是body或者html,原因是vue在挂载是会将对应的dom对象替换成新的div,但body和html是不适合替换的。
/**
* Query an element selector if it's not an element already.
*/
export function query (el: string | Element): Element {
if (typeof el === 'string') {
const selected = document.querySelector(el)
if (!selected) {
process.env.NODE_ENV !== 'production' && warn(
'Cannot find element: ' + el
)
return document.createElement('div')
}
return selected
} else {
return el
}
}
2.若vue实例中没有render,则将template编译成render:,也就是说vue只认render函数,同时,因为template可以写成多种形式,因此el也会转换成template(使用getOutHTML函数),再转换成render。
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
在getOuterHTML中,主要是获得el所对应的dom及其内容:
/**
* Get outerHTML of elements, taking care
* of SVG elements in IE as well.
*/
function getOuterHTML (el: Element): string {
if (el.outerHTML) {
return el.outerHTML
} else {
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
3. 最后,调用mount函数完成渲染
return mount.call(this, el, hydrating)
mountComponent函数的流程和功能
在runtime/index.js
中定义的$mount函数调用了mountComponent函数:
// public mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
在core/instance/lifecycle.js
中定义了mountComponent函数:
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
/* istanbul ignore if */
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
)
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
)
}
}
}
- 然后,定义updateComponent函数,该函数完成的功能是渲染和更新。至于什么时候调用这个函数,这个过程是使用观察者模式,由wacher一起完成的。
else {
warn(
'Failed to mount component: template or render function not defined.',
vm
)
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
- 在
src/core/observer/watach.js
中定义了Watcher
类,在构造函数中,传入的参数中有一个是否为渲染watcher,如果是,则将初始化实例的_watcher:
/**
* A watcher parses an expression, collects dependencies,
* and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
*/
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
然后,将getter赋值给expOrFn,这里传给expOrFn的是updateComponent
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
this.value = this.lazy
? undefined
: this.get()
在get函数中,主要是收集一些依赖,然后在初始化或者有更新时,调用this.getter(对应着updateComponent函数)
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
三、总结
在$mount中,首先是根据template将其转换为render(在没有render的情况下),然后调用mountComponent函数,该函数主要调用的是updateComponent函数。
updateComponent函数使用渲染watcher,在初次渲染或者值发生改变的时候调用该函数(这个过程由wacher完成),使用了观察者模式。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)