以一个常见场景为例:当使用 Vue 构建列表页面时,每次路由切换都需要发起数据请求。许多开发者习惯将请求全部置于 mounted 钩子中,但这并非最佳实践。核心判断在于请求是否依赖于真实 DOM——若无需 DOM 操作,那么选择 created 钩子会更合理,这是 Vue 组件生命周期优化的重要技巧。
这就涉及 Vue 组件生命周期与异步逻辑的协同配合。本质上只需关注两点:正确的触发时机与及时的清理操作。时机不当或清理缺失容易引发竞态条件、内存泄漏或视图更新失败等问题。

请求该放 created 还是 mounted?
核心取决于你对真实 DOM 的依赖程度:
- 纯数据获取,例如拉取用户信息或系统配置,不涉及任何 DOM 操作——应果断放 created。此时 data 已具备响应式能力,可以直接赋值,且触发时机早于 mounted,有助于为首屏数据做准备,这是提升页面加载性能的有效策略。
- 需要读取或操作真实 DOM,比如获取元素尺寸、初始化依赖 DOM 的第三方 UI 组件——则必须放在 mounted。此时
this.$el才可用,虚拟 DOM 已渲染为真实节点。 - 父子组件之间存在数据依赖,例如父组件需等待子组件挂载完成后才能发起请求——应放在父组件的 mounted 中,确保子组件已就绪。
异步结果“迟到”了怎么办?
多请求并发时,后发先至的竞态问题常导致数据错乱。快速切换列表页时,两次搜索请求返回顺序颠倒,旧数据覆盖新数据,页面出现混乱。可采用以下策略:
- 使用 AbortController 主动取消前序请求,尤其适用于 fetch 场景
- 在 Vue 3 的 onBeforeUnmount 中清理待处理的 Promise,更优雅地可借助
watchEffect+onInvalidate自动管理 - 为每个请求赋予唯一标识(如路由参数或时间戳),在响应返回时校验是否仍属于当前有效上下文
keep-alive 场景下的生命周期特殊处理
被 包裹的组件不会触发 beforeDestroy 和 destroyed,而是依靠 activated 和 deactivated 来切换状态:
- activated:首次进入或从缓存重新展示时触发。适合在此刷新数据,例如检查 ID 是否变化再决定是否重新拉取
- deactivated:组件隐藏但未销毁时触发。这是清理定时器、取消未完成请求、解绑全局事件(如 window.resize)的绝佳时机
- 注意:created 和 mounted 仅执行一次,后续切换不再触发,因此刷新逻辑不能只依赖这两个钩子
资源清理不能只依赖 destroyed
Vue 2 的 destroyed 或 Vue 3 的 onUnmounted 虽是最后一道防线,但许多资源其实应该更早释放:
- 定时器(
setInterval):在 beforeUnmount(Vue 3)或 beforeDestroy(Vue 2)中调用clearInterval - 事件监听(
addEventListener):务必对应调用removeEventListener,否则组件卸载后监听器仍驻留内存,容易造成内存泄漏 - 第三方库实例(如地图、图表插件):主动调用其 destroy 方法,并置空引用,以辅助垃圾回收
