vue的mount内容
本文最后更新于:1 个月前
Vue中的mount过程
Vue中的mount过程包含很多内容,因为Vue中是使用数据驱动模板的,所以我们写出来的模板是不能直接渲染到页面中的,那么mount过程中就需要解析模板转成可以被识别的内容。
vue runtime-only版本的$mount定义的位置
src/platforms/web/runtime/index.js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
判断是否有el和是否是浏览器环境,然后使用query函数去获取挂载点。
之后重点的挂载函数在mountComponent
src/core/instance/lifecycle.js
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
)
}
}
}
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// 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
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
在此函数中会使用callHook来调用我们两个生命周期(beforeMount,mounted)中定义的函数。
- 如果没有render会使用
vm.$options.render = createEmptyVNode
来赋予一个render函数,随后在const vnode = vm._render()
中调用render()获取对应的vnode,并调用_update来挂载实例,最后返回vm
在_init执行的最后会调用vm.$mount(vm.$options.el)
来自动挂载。
runtime with compiler版本的$mount定义的位置
src/platforms/web/entry-runtime-with-compiler.js
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
}
const options = this.$options
// resolve template/el and convert to render function
//如果存在render直接忽视template
if (!options.render) {
let template = options.template
//如果有template则用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
}
//如果没有template的话,就采用el的innerHtml
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
//进入src/compiler/to-function.js
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
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`vue ${this._name} compile`, 'compile', 'compile end')
}
}
}
return mount.call(this, el, hydrating)
}
处理过程
- 首先缓存下挂载的mount,之后重写$mount增加template转render的方法
- 然后处理el,调用query方法
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 } }
- 获得挂载点之后,先判断挂载点是否正确,因为模板替换的时候是直接全部替换掉,所以挂载点不能在body和html的位置,一旦挂载在这两个位置,那么html的基本结构就被破坏,浏览器无法处理。
- 判断在options內存在已经定义好的render函数,如果存在,那么后续操作都不需要了,毕竟结果已经被使用者定义好了
- 判断是否存在template,如果存在,则对template进行操作,如果不存在就去el中拿到对应的innerHTML,最终结果都是拿到对应的template模板
5.1 template如果传入的是字符串,就调用idToTemplate
缓存的同时获取节点上的innerHTML
5.2 template如果传入的是node节点,则直接去获取节点的innerHTMLconst idToTemplate = cached(id => { const el = query(id) return el && el.innerHTML })
- 最后template有内容的话调用compileToFunctions方法把模板转换成render函数
src/compiler/to-function.js
这里使用compile函数去将template转换成ast,然后使用generate函数将ast转换成对应的render函数export function createCompileToFunctionFn (compile: Function): Function { const cache = Object.create(null) //将template转成render函数 return function compileToFunctions ( template: string, options?: CompilerOptions, vm?: Component ): CompiledFunctionResult { options = extend({}, options) const warn = options.warn || baseWarn delete options.warn /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production') { // detect possible CSP restriction try { new Function('return 1') } catch (e) { if (e.toString().match(/unsafe-eval|CSP/)) { warn( 'It seems you are using the standalone build of Vue.js in an ' + 'environment with Content Security Policy that prohibits unsafe-eval. ' + 'The template compiler cannot work in this environment. Consider ' + 'relaxing the policy to allow unsafe-eval or pre-compiling your ' + 'templates into render functions.' ) } } } // check cache const key = options.delimiters ? String(options.delimiters) + template : template if (cache[key]) { return cache[key] } // compile const compiled = compile(template, options) // check compilation errors/tips if (process.env.NODE_ENV !== 'production') { if (compiled.errors && compiled.errors.length) { if (options.outputSourceRange) { compiled.errors.forEach(e => { warn( `Error compiling template:\n\n${e.msg}\n\n` + generateCodeFrame(template, e.start, e.end), vm ) }) } else { warn( `Error compiling template:\n\n${template}\n\n` + compiled.errors.map(e => `- ${e}`).join('\n') + '\n', vm ) } } if (compiled.tips && compiled.tips.length) { if (options.outputSourceRange) { compiled.tips.forEach(e => tip(e.msg, vm)) } else { compiled.tips.forEach(msg => tip(msg, vm)) } } } // turn code into functions const res = {} const fnGenErrors = [] res.render = createFunction(compiled.render, fnGenErrors) res.staticRenderFns = compiled.staticRenderFns.map(code => { return createFunction(code, fnGenErrors) }) // check function generation errors. // this should only happen if there is a bug in the compiler itself. // mostly for codegen development use /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production') { if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) { warn( `Failed to generate render function:\n\n` + fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'), vm ) } } return (cache[key] = res) } }
src/compiler/index.js
generate函数使用genElement将ast转换成对应的结构,genElement的相关代码也在此文件中。export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions ): CompiledResult { const ast = parse(template.trim(), options) if (options.optimize !== false) { optimize(ast, options) } const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } })
src/compiler/codegen/index.js
export function generate ( ast: ASTElement | void, options: CompilerOptions ): CodegenResult { const state = new CodegenState(options) // fix #11483, Root level <script> tags should not be rendered. const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")' return { render: `with(this){return ${code}}`, staticRenderFns: state.staticRenderFns } }
- 最后调用缓存下来的mount去渲染dom
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!