前言
开发中遇到的问题:在一个使用了多个图表的页面中,数据量很大,并且接口还要定时获取,将 获取图表数据的方法写在mounted()里面了,页面在使用时,发现 容易出现卡顿问题,于是,进行了代码排查及优化。
其中,我优化的一个点:就是将获取图表及列表数据的方法 放在created里面去调用了。
现在,我来解释一下为什么在这种场景下,将接口请求放在 created 中是更优的选择,以及为什么它不会对页面渲染产生负面影响。
1. 为什么从 mounted 移到 created?—— 为了“更快”
- 并行工作:当我们将 API 请求放在 created 钩子中时,组件实例一被创建,数据请求的指令就立刻发出去了。然后,Vue 会继续执行接下来的生命周期,包括模板编译和 DOM 挂载。
- 节省等待时间:在这个过程中,数据请求(这是一个网络I/O操作,比较耗时)和 DOM 渲染(这是一个CPU操作)是并行进行的。等到 mounted 钩子执行,DOM 渲染完成时,我们的数据很可能已经从服务器返回了。这样,用户就能更快地看到最终呈现了数据的页面。
- 反之:如果放在 mounted 里,必须等 DOM 完全渲染好之后,才开始发送数据请求。这就意味着 “渲染” 和 “数据请求” 是串行进行的,用户会先看到一个空的骨架页面,然后再等待数据返回并填充,总体的“白屏”或“空内容”时间会更长。
2. 会对页面渲染有影响吗?—— 不会,因为是异步的
这是最关键的一点。
- API 请求是异步的:当你调用 this.getList() 时,它内部的 getHumiture(param) 会返回一个 Promise。JavaScript 不会傻傻地“卡住”并等待网络请求返回。它会立即继续执行后续代码,也就是完成 created 钩子,然后进入 mounted 钩子去渲染 DOM。
- Vue 的响应式系统:
- 初始渲染时,DOM 会根据 data 中的初始值(例如一个空数组 [])进行渲染。所以用户会先看到一个空的表格或图表。
- 几百毫秒或几秒后,当 getHumiture 的 Promise 完成,.then() 里的回调函数被执行。
- 在这个回调函数里,你执行了 this.humitureData = res.data 这样的操作。
- Vue 的响应式系统会侦测到 humitureData 这个数据的变化,并自动地、高效地去更新 DOM 中依赖了这个数据的部分。
前言总结
created | mounted | |
执行时机 | 实例已创建,DOM 未渲染 | 实例已挂载,DOM 已渲染 |
能否访问DOM | ❌ 不能 (e.g., this.$refs 是 undefined) | ✅ 可以 |
适合做的事 | 初始化数据(API请求)、开启定时器、事件监听 | 操作DOM、集成需要DOM的第三方库 (e.g., ECharts 初始化) |
性能表现 | 更优,数据请求与DOM渲染并行 | 稍慢,数据请求在DOM渲染后才开始 |
一、核心区别与对比
created | mounted
特性 |
created |
mounted |
执行时机 |
Vue 实例已创建,但 DOM 尚未生成 |
Vue 实例已挂载到页面上,DOM 已生成 |
数据访问 |
✅ 可以访问 data、props、computed、methods |
✅ 可以访问 data、props、computed、methods |
DOM 访问 |
❌ 不能。this.$el 不存在,this.$refs 为空对象 |
✅ 可以。能安全地访问和操作所有 DOM 元素 |
服务器端渲染(SSR) |
✅ 会执行 |
❌ 不会执行 |
核心用途 |
初始化数据状态,进行与 DOM 无关的异步操作 |
执行依赖 DOM 的操作 |
二、使用场景分析:“应该放在哪里?”
你可以根据这个简单的逻辑来判断:“我的这个操作,需要用到页面上的真实 DOM 元素吗?”
场景 1:应该放在 created 的情况(通用 & 推荐)
核心原则:所有与 DOM 无关的初始化操作,都应该优先放在 created。
1. 发起 API 请求获取数据(最常见)
- 为什么? 这能让数据请求与 DOM 渲染并行,用户能更快看到最终内容。这是提升首屏加载性能的关键一步。
created() { // 获取用户列表,完全不需要 DOM this.fetchUserList(); }
2. 初始化非响应式的数据或状态
- 为什么? 此时实例已经创建,可以安全地挂载一些属性。
created() { this.myTimer = null; // 初始化一个定时器变量 this.eventBusListener = () => { /* ... */ }; // 定义一个事件监听器 }
3. 开启定时器 (setInterval) 或事件总线监听 (eventBus.$on)
- 为什么? 这些操作同样不依赖 DOM,越早启动越好。(注意:一定要在 beforeDestroy 中销毁它们!)
- 示例:
created() {
this.myTimer = setInterval(this.fetchUpdates, 5000);
}
beforeDestroy() {
clearInterval(this.myTimer);
}
场景 2:必须放在 mounted 的情况(特殊 & 依赖 DOM)
核心原则:任何需要读取或修改页面上真实 DOM 元素的操作,必须放在 mounted。
1. 初始化需要 DOM 的第三方库(最常见)
- 为什么? 像 ECharts、D3.js、CodeMirror 等库,初始化时都需要一个已经存在的 DOM 容器作为挂载点。
- 示例:
mounted() { // ECharts 需要一个 div 容器,这个 div 必须在 mounted 后才存在 const myChart = echarts.init(this.$refs.mainChart); myChart.setOption(/* ... */); }
2. 直接操作 DOM
- 为什么? 这是 mounted 的本职工作。比如获取某个元素的尺寸、位置,或者手动添加/移除 class。
- 示例:
mounted() { const componentWidth = this.$el.clientWidth; // 获取组件根元素的宽度 const specificDiv = document.getElementById('my-div'); // 使用原生 API }
3. 需要访问子组件的 DOM 或方法
- 为什么? 通过 this.$refs 访问子组件实例,并调用其方法或访问其 DOM,必须确保子组件也已经被挂载。
- 示例:
mounted() { // 调用子组件暴露的方法 this.$refs.myChildComponent.focusInput(); }
三、总结与最佳实践
- 养成习惯:默认将所有数据请求 (axios, fetch) 都放在 created 里。这几乎总是一个更好的选择。
- 明确界限:只有当你写下 this.$refs、this.$el、document.getElementById,或者需要初始化一个图表/编辑器这类需要“画布”的库时,才把代码放进 mounted。
- 注意 this.$nextTick:有时在 created 或 methods 中修改数据后,你想立即访问更新后的 DOM,此时 mounted 帮不了你,你需要使用 this.$nextTick。
this.list.push('newItem'); this.$nextTick(() => { // 在这里可以访问到渲染了 'newItem' 后的 DOM const lastItem = this.$refs.list.lastChild; });