返回专题首页

Vue 专题

生命周期与副作用:created、mounted、watch、watchEffect 与清理时机

很多 Vue 页面之所以越来越难维护,不是因为模板复杂,而是因为副作用散得到处都是。请求在某个钩子里发,事件监听在另一个地方绑,定时器忘了清,`watch` 又到处响应,最后页面能跑,但没人说得清哪些逻辑什么时候发生、依赖谁、页面离开后会不会留下脏状态。

Vue 专题第 06 篇 / 26 篇6 分钟

很多 Vue 页面之所以越来越难维护,不是因为模板复杂,而是因为副作用散得到处都是。请求在某个钩子里发,事件监听在另一个地方绑,定时器忘了清,watch 又到处响应,最后页面能跑,但没人说得清哪些逻辑什么时候发生、依赖谁、页面离开后会不会留下脏状态。

所以这一篇真正要讲的,不只是生命周期名字,而是 Vue 页面里的“副作用治理”。也就是:什么时候做什么事,为什么放在这里,什么时候必须清理,哪些响应式监听应该存在,哪些其实是在制造混乱。

什么叫副作用?

从 Vue 项目视角看,凡是“不是单纯根据状态生成界面”的动作,都可以视为副作用,比如:

  • 发请求;
  • 订阅事件;
  • 操作浏览器 API;
  • 设置定时器;
  • 手动同步外部状态;
  • 写日志或埋点;
  • 根据某个变化触发额外流程。

副作用本身不可怕,页面几乎不可能完全没有副作用。真正可怕的是副作用没有被放在正确的时机和边界里。

生命周期为什么是副作用的第一层边界?

Vue 组件并不是一上来就“完整存在”的。它会经历创建、挂载、更新和卸载等阶段。生命周期的价值,就在于告诉你:某段逻辑应该在什么时候出现。

更重要的是,它让你区分这几件事:

  • 逻辑依赖的是响应式数据,还是依赖真实 DOM;
  • 行为只需要执行一次,还是要跟随状态不断变化;
  • 页面离开时是否必须清理。

如果不先分清这些事情,生命周期钩子和 watch 很快就会被混用。

createdmounted 这类钩子最重要的区别是什么?

很多人会机械记忆“哪个先执行、哪个后执行”,但更有价值的判断是:

  • 此时组件状态是否已经准备好;
  • DOM 是否已经可用;
  • 我是不是必须依赖浏览器环境或节点尺寸。

created 更偏初始化和数据准备

它适合那些不依赖真实 DOM 的初始化逻辑,比如:

  • 整理初始参数;
  • 组装本地状态;
  • 做部分早期数据准备。

mounted 更偏需要真实挂载结果的逻辑

比如:

  • 依赖 DOM 尺寸;
  • 需要访问浏览器对象;
  • 需要初始化某些与页面节点强绑定的第三方实例。

真正成熟的判断,不是背“推荐写在哪”,而是知道当前逻辑到底依赖什么条件。

为什么副作用不能都塞进生命周期钩子?

因为不是所有变化都只发生一次。真实页面里经常会有:

  • 路由参数变化后重新拉取;
  • 分页变化后重新查询;
  • 某个筛选条件改变后联动更新;
  • 权限或主题变化后同步外部表现。

这些场景如果都只靠某个生命周期钩子,很快就会发现它根本不够表达。于是才需要 watchwatchEffect 这些围绕响应式依赖变化来组织副作用的能力。

watch 真正适合什么?

watch 更适合那些“我很明确知道要盯哪份状态,并在它变化时执行某个副作用”的场景。它的优点在于依赖清楚、触发条件明确,特别适合:

  • 监听路由参数;
  • 监听分页、筛选条件;
  • 监听某个开关后触发额外请求;
  • 在状态变化时同步外部系统。

也正因为如此,watch 比较适合“我知道自己在等什么变化”的场景。

watchEffect 又适合什么?

watchEffect 更像是“执行一段依赖收集驱动的副作用”。它的优势在于写法直接,适合那些依赖关系自然从代码中流出来的场景。但它也更容易让逻辑边界变模糊。

一个更稳的经验通常是:

  • 依赖来源清晰、触发条件需要可控时,优先 watch
  • 副作用逻辑短、依赖很自然时,再考虑 watchEffect
  • 一旦逻辑开始复杂,优先回到更可读的显式结构。

很多页面难排查,就是因为监听逻辑被写得“很自动”,最后谁触发了什么反而更难看清。

为什么清理时机这么重要?

副作用最容易留下的问题,往往不是“没执行”,而是“执行完了却没结束”。比如:

  • 页面卸载后事件监听还在;
  • 定时器继续跑;
  • 旧请求回来把新页面状态覆盖掉;
  • 组件销毁后还在试图更新状态。

这些问题短期可能不明显,但在复杂页面、弹窗、多次切换和长时间运行场景里非常常见。清理副作用,本质上是在管理页面生命周期之外的残留影响。

把生命周期和副作用放回项目里,怎样组织更稳?

一个更成熟的顺序通常是:

1. 先区分这段逻辑是纯计算还是副作用;2. 再区分它是一次性初始化,还是跟随状态变化;3. 如果依赖 DOM 或浏览器环境,放到合适时机;4. 如果会留下持续影响,就一定要设计清理路径;5. 不要把多个互不相关的副作用全塞进同一处。

这套顺序比背生命周期表有用得多,因为它更接近真实项目里的决策过程。

最常见的几个误区

1. 所有逻辑都往 mounted

结果页面一复杂,副作用顺序和依赖关系会越来越难看。

2. 看到状态变化就条件反射写 watch

很多逻辑其实应该是派生状态,而不是副作用监听。

3. watchEffect 用得很顺手,但边界越来越模糊

短期省代码,长期很难排查。

4. 事件监听、定时器、订阅不清理

这类问题最容易在弹窗和长生命周期页面里积累。

总结

Vue 生命周期真正重要的,不是记住钩子名称,而是学会管理副作用:什么时候初始化、什么时候依赖 DOM、什么时候跟随状态变化、什么时候必须清理。createdmountedwatchwatchEffect 并不是互相替代的工具,而是用来表达不同副作用边界的手段。只要你把“时机、依赖、持续性、清理”这四件事想清楚,页面逻辑就会稳定很多。