一、先搞懂传统 onload 和 Vue 组件生命周期的区别
做前端开发时,很多同学刚从传统网页开发转到 Vue2 项目,总会疑惑:“以前用 window.onload 等加载完成后执行逻辑,Vue2 里该咋实现类似功能?”毕竟 Vue 是组件化、数据驱动的框架,和传统网页的加载逻辑不太一样,这篇就从组件生命周期、异步处理、原生事件等角度,把 Vue2 里“替代 onload”的门道讲清楚。
传统网页里,window.onload
或 $(document).ready()
(jQuery)是等整个页面资源(图片、脚本、样式等)加载完,再执行逻辑,但 Vue 是单页应用(SPA)+ 组件化开发,核心是“数据驱动视图”,组件有自己的生命周期。
举个例子:传统页面加载是“先加载资源,再渲染页面”;Vue 组件则是“先初始化组件实例 → 处理数据 → 渲染 DOM → 挂载到页面”,Vue 里没有直接和 window.onload
完全对应的 API,得结合组件生命周期和场景需求来实现类似效果。
Vue2 中最常用的“替代 onload”方式:mounted 生命周期
Vue2 组件的 mounted
钩子,是组件“挂载完成”的标志——此时组件对应的 DOM 已经渲染到页面上了,大部分场景下,用 mounted
就能替代传统 onload 处理“DOM 加载完成后执行逻辑”的需求。
举个简单例子:页面有个 <div ref="myDiv"></div>
,要在 DOM 加载完后给它设置内容,代码可以这么写:
export default { mounted() { this.$refs.myDiv.innerHTML = 'DOM 加载完啦,现在能操作我~'; } }
但要注意!mounted
只保证组件自身的 DOM 渲染完成,如果组件里有异步数据(比如用 axios 发请求拿数据,再渲染到 DOM),mounted
执行时可能数据还没回来,DOM 也没更新,这时候就得用到 $nextTick
了。
处理异步数据后“模拟 onload”:$nextTick 的作用
Vue 是异步更新 DOM的——当数据变化时,Vue 不会立刻更新 DOM,而是把更新操作放进“队列”,等所有数据变化完成后,再统一更新 DOM(这样做是为了性能优化),所以如果在 mounted
里改了数据,想立刻操作更新后的 DOM,直接写代码会“失效”,因为 DOM 还没真正更新。
这时候 this.$nextTick
就派上用场了:它会把回调函数放到“下一次 DOM 更新循环”之后执行,保证回调执行时,DOM 已经是最新的。
举个异步数据的例子:组件里用 axios 拿列表数据,渲染成 <ul><li>
,然后要统计 li 的数量,代码得这么写:
export default { data() { return { list: [] } }, mounted() { axios.get('/api/list').then(res => { this.list = res.data; // 数据更新,但DOM还没更新 this.$nextTick(() => { // 这里DOM已经更新,能拿到li的数量 const liCount = this.$refs.ulRef.querySelectorAll('li').length; console.log('li数量:', liCount); }); }); } }
简单说,$nextTick
是帮我们“等 DOM 真正更新后,再执行逻辑”,完美解决“数据异步更新后,操作最新 DOM”的问题,这也是 Vue 里模拟“数据 + DOM 都加载完”的关键技巧。
页面级“onload”:路由导航守卫 + 组件生命周期
如果是单页应用(SPA),整个“页面”其实是路由切换后的组件,想在“整个页面加载完成”后执行逻辑(比如统计页面加载时间、初始化全局插件),得结合路由导航守卫和组件生命周期。
比如用 beforeRouteEnter
守卫(路由进入前触发,此时组件实例还没创建),配合组件的 mounted
:
// 路由组件里的代码 export default { beforeRouteEnter(to, from, next) { // 这里拿不到 `this`(组件实例还没创建) next(vm => { // vm 是组件实例,路由进入且组件挂载完成后执行 vm.handlePageLoad(); // 调用组件内的方法 }); }, mounted() { this.handlePageLoad(); // 也可以直接在mounted里写逻辑 }, methods: { handlePageLoad() { console.log('页面(路由组件)加载完成,执行初始化逻辑~'); } } }
如果页面有多个异步请求,想等所有请求完成后再执行逻辑,可以用 Promise.all
封装请求,在 mounted
里等待所有请求完成后,再执行后续操作。
mounted() { const request1 = axios.get('/api/data1'); const request2 = axios.get('/api/data2'); Promise.all([request1, request2]).then(([res1, res2]) => { // 两个请求都完成,再处理DOM或业务逻辑 this.initPage(res1.data, res2.data); }); }
结合原生 onload 事件:给元素绑定 load 事件
传统 onload 还有个常见场景:等某个资源(比如图片、iframe)加载完成后执行逻辑,Vue 里可以直接给元素绑定原生 load
事件。
比如加载图片时,要等图片加载完再显示预览:
<template> <img :src="imgUrl" @load="handleImgLoad" alt="预览图" style="display: none;" > <div v-if="imgLoaded">图片加载完成,现在显示~</div> </template> <p><script> export default { data() { return { imgUrl: 'https://example.com/big-img.jpg', imgLoaded: false } }, methods: { handleImgLoad() { this.imgLoaded = true; this.$refs.img.style.display = 'block'; // 显示图片 } } } </script>
再比如 iframe 加载完成后执行逻辑:
<iframe :src="iframeUrl" @load="handleIframeLoad" ref="iframeRef" ></iframe>
这种方式和传统 onload 逻辑几乎一样,只是在 Vue 里用 @load
(v-on:load)来绑定事件,更符合 Vue 的语法习惯。
特殊场景:第三方库初始化依赖 DOM 加载完成
开发中经常遇到这类需求:用 ECharts 画图表、用 Swiper 做轮播、用百度地图 SDK 嵌入地图……这些库都需要先有 DOM 容器,再初始化实例,这时候就得结合 mounted
和 $nextTick
来处理。
以 ECharts 为例,步骤一般是:
- 在模板里写 DOM 容器:
<div ref="chartRef" style="width: 600px; height: 400px;"></div>
- 在
mounted
里初始化 ECharts,但如果有异步数据,要等数据和 DOM 都 ready:
import * as echarts from 'echarts'; <p>export default { data() { return { chartData: [] // 异步获取的图表数据 } }, mounted() { axios.get('/api/chart-data').then(res => { this.chartData = res.data; this.$nextTick(() => { // 初始化图表 const chartDom = this.$refs.chartRef; const myChart = echarts.init(chartDom); myChart.setOption({ // 基于this.chartData配置option xAxis: { type: 'category', data: this.chartData.x }, yAxis: { type: 'value' }, series: [{ data: this.chartData.series, type: 'bar' }] }); }); }); } }
这里的关键是:第三方库依赖 DOM 存在 + 数据完整,所以要等 mounted
(DOM 存在)+ 异步数据回来 + $nextTick
(DOM 因数据更新后 ready),三者都满足后再初始化。
Vue2 里“模拟 onload”要分场景选方法
Vue2 里没有和传统 window.onload
完全一样的 API,因为框架设计思路不同,但我们可以根据场景选最合适的方案:
- 如果是组件自身 DOM 加载完就执行逻辑 → 用
mounted
钩子。 - 如果是异步数据更新后,操作最新 DOM → 用
mounted + $nextTick
(或watch
数据变化后用$nextTick
)。 - 如果是整个页面(路由组件)加载完 → 用路由导航守卫 + 组件 mounted,或结合多请求的
Promise.all
。 - 如果是单个资源(图片、iframe)加载完 → 给元素绑定
@load
事件。 - 如果是第三方库依赖 DOM/数据 → 用
mounted + $nextTick + 异步数据处理
。
理解 Vue 的生命周期、异步更新 DOM 机制,再结合场景拆分需求,就能灵活实现“类似 onload”的功能啦~要是刚开始分不清什么时候用 mounted
什么时候用 $nextTick
,多写几个例子试试,就能get到区别啦!
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。