Vue2里的mount到底是干啥的?怎么用?要注意啥?
说起Vue2里的mount,不少刚开始学的同学都会犯懵:它到底是生命周期里的“挂载阶段”,还是手动调用的$mount方法?和页面渲染、DOM操作有啥关系?为啥有时候mount完了拿不到DOM?今天咱们把mount相关的知识点拆碎了讲,从概念到用法,再到避坑和优化,一次性理清楚~
mount在Vue2生命周期里扮演啥角色?
Vue实例从“诞生”到“显示在页面”,要经历生命周期钩子的串联,mount阶段就是把“虚拟的Vue实例”和“真实的DOM元素”绑定起来的核心环节,流程分两步:
第一步是beforeMount钩子:此时Vue已经完成数据观测、事件初始化,但模板还没编译成渲染函数,页面上的$el
还是“占位符”(比如new Vue时指定的el
对应的DOM元素,此时还没被Vue渲染后的内容替换),举个例子,你在beforeMount
里打印this.$el.innerHTML
,会发现内容还是页面上原本写的静态HTML,没被Vue处理过。
第二步是mount过程+mounted钩子:Vue会先把模板(不管是template
选项、el
对应的HTML,还是render
函数)编译成渲染函数,再通过渲染函数生成虚拟DOM,最后把虚拟DOM转换成真实DOM,替换掉之前的$el
占位符,等这一系列操作完成,就会触发mounted
钩子——这时候操作DOM(比如用refs
拿元素、修改样式)才真正“生效”,因为DOM已经实实在在挂到页面上了。
另外要注意:如果new Vue时写了el: '#app'
,Vue会自动调用$mount方法完成挂载;如果没写el
,就需要手动调用vm.$mount('#app')
来触发挂载,这也是“mount既代表生命周期阶段,又代表$mount方法”的原因~
手动调用$mount和自动挂载有啥不同?
先看自动挂载:写new Vue({ el: '#app', ... })
时,Vue会自动找到页面上的#app
元素,把实例挂载上去,这种方式适合“页面初始化就渲染”的场景,比如项目入口文件里的根实例。
再看手动调用$mount:场景更灵活,比如做组件库、动态创建弹窗组件时,你可能需要“先创建Vue实例,再决定啥时候挂到页面”,举个代码例子:
// 先创建实例,此时不自动挂载 const vm = new Vue({ template: '<div>{{ msg }}</div>', data() { return { msg: '手动挂载测试' }} }) // 手动选择挂载时机,比如用户点击按钮后 button.addEventListener('click', () => { vm.$mount('#app') // 挂载到#app元素 // 或者挂载到文档碎片,之后自己插入页面 // const div = document.createElement('div') // vm.$mount(div) // document.body.appendChild(div) })
另外要注意Vue版本差异:Vue有“完整版(带编译器)”和“运行时版”,完整版能把template
字符串编译成渲染函数,所以手动挂载时用template
没问题;但运行时版没有编译器,必须用render
函数,否则会报错,比如运行时版里写:
new Vue({ // 运行时版这样写会报错!因为没编译器处理template template: '<div>{{ msg }}</div>', data() { return { msg: 'hi' }} }).$mount('#app')
这时候得改成render
函数:
new Vue({ render: h => h('div', {}, this.msg), data() { return { msg: 'hi' }} }).$mount('#app')
mount过程中,DOM是怎么“从无到有”的?
很多同学好奇:写个<template><div>{{ msg }}</div></template>
,怎么就变成页面上的真实div
了?这背后是“模板编译→渲染函数→虚拟DOM→真实DOM”的流程:
-
模板编译:Vue先把
template
字符串(或el
对应的HTML)转换成抽象语法树(AST),比如<div>{{ msg }}</div>
会被解析成描述标签、属性、插值的AST节点,接着Vue会优化AST,标记出“静态节点”(比如纯文本、不随数据变化的元素),这些节点后续渲染时可以跳过更新,提升性能,最后把AST转换成渲染函数(render function),比如上面的例子会生成类似h('div', {}, this.msg)
的函数(h
是createElement
的别名)。 -
生成虚拟DOM:调用渲染函数,生成虚拟DOM树(本质是JS对象,描述DOM的结构、属性、事件等),虚拟DOM的好处是“轻量”,操作JS对象比直接操作真实DOM快得多。
-
挂载真实DOM:Vue会对比虚拟DOM和页面上的
$el
占位符,通过patch过程把虚拟DOM转换成真实DOM,替换掉原来的$el
,完成挂载,这一步完成后,mounted
钩子就会触发,页面上就能看到渲染后的内容啦~
为啥mount后有时候拿不到DOM?常见坑怎么避?
遇到“mount完了,DOM还是空的/拿不到refs
”,大概率是时机或作用域没搞对,常见场景和解决方法:
场景1:在beforeMount
里操作DOM
beforeMount
阶段,模板还没编译成渲染函数,$el
还是页面上的原始元素,没被Vue替换。
new Vue({ el: '#app', beforeMount() { console.log(this.$el.innerHTML) // 打印的是页面上#app原本的内容,不是Vue渲染后的 } })
解决:要操作DOM,至少等mounted
钩子,因为mounted
时DOM已经替换完成。
场景2:mounted
里改数据,DOM没及时更新
Vue的DOM更新是异步的,比如mounted
里修改数据,Vue会等所有同步代码执行完,再统一更新DOM,如果这时候立刻操作DOM,拿到的还是旧内容,举个例子:
mounted() { this.msg = '新内容' console.log(this.$el.innerHTML) // 可能还是旧内容,因为DOM更新还没执行 }
解决:用this.$nextTick
,等DOM更新后再操作:
mounted() { this.msg = '新内容' this.$nextTick(() => { console.log(this.$el.innerHTML) // 此时能拿到更新后的内容 }) }
场景3:组件嵌套时,父组件mounted
里访问子组件DOM
父组件和子组件的生命周期顺序是:父beforeMount → 子beforeMount → 子mounted → 父mounted,所以父组件mounted
时,子组件(同步加载的)已经完成挂载,理论上可以访问子组件的$el
或refs
;但如果子组件是异步组件(比如用import()
加载),父组件mounted
时子组件可能还没加载完成,这时候访问就会空。
解决:如果是普通子组件,父mounted
里可以安全访问;如果是异步子组件,要么用v-if
控制加载状态(等子组件加载完再渲染父组件部分DOM),要么在子组件的mounted
里给父组件发通知,再执行DOM操作。
组件化开发中,mount有啥特殊点?
在组件化开发里,每个组件都是独立的Vue实例,所以mount的逻辑会嵌套执行:
-
父组件与子组件的mount顺序:父组件执行
beforeMount
→ 找到子组件标签,创建子组件实例 → 子组件执行beforeMount
→ 子组件mounted
→ 父组件mounted
,所以父组件mounted
时,所有同步加载的子组件已经完成挂载,这时候父组件里操作子组件的DOM是安全的。 -
异步组件的mount:如果子组件是异步加载的(比如
const Child = () => import('./Child.vue')
),父组件的mounted
会先执行,等异步组件加载完成后,才会执行子组件的beforeMount
和mounted
,这时候父组件里如果想访问异步子组件的DOM,必须等子组件加载+挂载完成,可以用<Child v-if="isLoaded" />
控制加载状态,或者在异步组件的mounted
里通知父组件。 -
递归组件的mount:如果组件自己调用自己(比如树形组件),mount过程会递归执行,直到所有层级的组件都挂载完成,这时候要注意内存溢出问题,避免无限递归。
和mount有关的性能优化技巧
想要页面渲染更快、减少mount时的性能开销,可以从这几个角度下手:
技巧1:减少mounted
里的同步 heavy 操作
mounted
是DOM挂载完成后触发,如果你在这钩子函数里写大量同步逻辑(比如循环计算、DOM频繁操作),会阻塞页面渲染,让用户觉得“页面卡”。解决:把同步逻辑拆成异步(比如用setTimeout
、Promise
),或者移到created
钩子(created
时数据已初始化,但DOM还没挂载,适合做数据请求、逻辑计算)。
技巧2:用keep-alive
缓存组件,减少重复mount
<keep-alive>
是Vue的内置组件,能缓存组件实例,避免组件反复mount
和unmount
,比如Tab切换组件,切换时被隐藏的组件不会销毁,下次切换回来时直接从缓存取,不用重新mount
,用法:
<keep-alive> <component :is="activeComponent"></component> </keep-alive>
技巧3:优化模板,减少编译开销
Vue编译模板时,会遍历所有节点,如果模板里有大量静态节点(比如纯展示的图片、文字,不随数据变化),可以用v-once
标记,让Vue编译时优化这部分,后续更新时跳过。
<div v-once>{{ 静态文本 }}</div> <img v-once src="logo.png" />
技巧4:用函数式组件替代普通组件
函数式组件没有自己的实例(少了data
、生命周期钩子等),所以mount
过程更轻量,适合纯展示、无状态的组件(比如列表项、图标组件),定义方式:
Vue.component('FunctionalComp', { functional: true, render(h, context) { return h('div', context.props.msg) } })
Vue2的mount是“把虚拟实例落地成真实DOM”的核心环节,既涉及生命周期的关键步骤,又有$mount方法的灵活用法,理解它的流程、避坑点和优化技巧,能帮你更丝滑地写Vue代码~下次再遇到“mount后DOM没更新”“手动挂载怎么用”这类问题,就知道从哪下手啦~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。