1. 生命周期到底在解决什么问题
1.1 组件不是一锤子买卖
我刚带团队的时候,发现很多前端同学对Vue生命周期的理解,停留在"背出那8个钩子函数名字"的层面。你问beforeCreate和created有什么区别,他能答上来"一个拿不到data,一个能拿到data"。再追问一句"那为什么要在created里请求数据、不能放到beforeCreate吗",基本就卡住了。这种状态去面试,碰上稍微会问的面试官,十有八九要翻车。
实际上,Vue生命周期设计的本质,是把组件从创建到销毁的全过程,拆分成若干个可控的阶段,并且在每个阶段给你一个"插脚"的机会。你可以把它想象成一个产品的生产流水线:原材料进厂、加工、质检、包装、出厂,每个环节都有一个工位,工位上的人可以在自己负责的环节做该做的事。如果你非要跑到前一个工位去做后一个工位的事,要么东西还没准备好,要么做完了也会被后面的流程覆盖掉。
组件也一样。数据还没初始化的时候你去操作data,拿不到;DOM还没渲染出来的时候你去操作DOM,找不到节点;组件已经销毁了你还在里面跑定时器,轻则内存泄漏,重则报错。生命周期这一套机制,就是把"什么时候能干什么事"这个约束给显式化、规范化了。
所以,面试官问你生命周期,表面上是在考察你对Vue API的熟悉程度,本质上是在考察两件事:第一,你知不知道Vue内部在组件各个阶段做了什么;第二,你在实际开发中能不能根据这些阶段的特性,写出没有低级Bug的代码。这两点,恰恰是"背答案型"选手最容易露馅的地方。
1.2 Vue设计生命周期的核心逻辑
Vue的生命周期设计,遵循一条非常清晰的逻辑线:数据准备 → 模板编译 → DOM挂载 → 数据变化 → DOM更新 → 组件卸载。这是一个单向的、不可逆的过程(除非你配合keep-alive做缓存,那是另一套机制)。
我们平时说的8个钩子函数,就是在这条逻辑线上的关键节点埋下的"通知回调"。Vue在内部完成某个关键动作之后,会主动调用你注册的钩子函数,把控制权交还给你。你可以在这些节点里塞自己的业务逻辑,Vue不会管你干什么,但它保证的是:调用某个钩子时,组件一定处于某个确定的状态。
举个例子。beforeCreate被调用时,Vue实例刚刚完成初始化,但数据响应式还没建立,methods、computed、watch也都还没初始化。created被调用时,数据响应式已经建立好了,但模板还没编译,DOM也还没生成。mounted被调用时,组件已经挂载到真实DOM上了。这些"状态保证",就是生命周期钩子存在的意义。
理解了这个逻辑,很多面试题的答案就不用背了。问"created和mounted的区别",你只需要说清楚:created时能拿到数据、拿不到DOM;mounted时数据和DOM都准备好了。问"为什么mounted里操作DOM没问题、created里就报错",你只需要说:因为created时组件还没挂载,this.$el还不存在。逻辑通了,答案自然就出来了。
2. 八大钩子函数逐段拆解
2.1 创建阶段:beforeCreate与created
先看一张我总结的阶段对应表,后面逐段讲。
| 钩子函数 | 组件状态 | 能做什么 | 不能做什么 |
|---|---|---|---|
| beforeCreate | 实例初始化前 | 极少用,可做全局配置 | 访问data、methods、computed |
| created | 数据响应式建立后 | 请求数据、初始化非DOM依赖 | 访问DOM、操作this.$el |
| beforeMount | 模板编译完成后、挂载前 | 最后一次修改data的机会 | 访问真实DOM |
| mounted | DOM挂载完成后 | 操作DOM、初始化DOM插件 | 无(但注意子组件是否就绪) |
| beforeUpdate | 数据变化后、DOM更新前 | 获取更新前的状态 | 依赖更新后的DOM |
| updated | DOM更新完成后 | 操作更新后的DOM | 频繁触发,不要做重操作 |
| beforeDestroy | 组件销毁前 | 清理定时器、解绑事件 | 依赖组件后续状态 |
| destroyed | 组件销毁后 | 最终清理 | 操作已销毁的组件 |
beforeCreate在日常业务代码里几乎不会用到,但它有个典型的使用场景:在mixins或插件里做一些不依赖组件数据的初始化操作。比如我早期做项目时,在beforeCreate里给组件实例挂载一个自定义事件总线,或者初始化一些全局配置参数。因为这个阶段Vue啥都没准备好,你挂的东西不会和Vue自身的初始化逻辑冲突。
created则是高频钩子。在这个阶段,data已经变成响应式的了,methods里的方法也都能用了,computed和watch也初始化完了。最典型的用法就是在created里发请求拿数据。为什么不是beforeCreate?因为那时候data还是空的,你拿到的数据没地方放。为什么不是mounted?因为mounted要等DOM挂载完,对于首屏渲染来说,等DOM挂完再发请求,白白浪费了从发请求到DOM渲染之间这段时间。在created里发请求,数据回来之前DOM可能刚好渲染完,两边并行,体验更好。
不过这里有个细节值得注意:created里能访问data,但这不代表你在created里对data做的修改,一定能在首屏渲染中体现。因为模板编译和DOM挂载是后面才做的事,你改的响应式数据会被收集起来,等挂载完成后统一应用到DOM上。实际上,在created里修改数据,是能影响首屏内容的,这也是"created里发请求、拿到数据后赋值给data"能生效的原因。
2.2 挂载阶段:beforeMount与mounted
beforeMount触发的时候,Vue已经完成了模板编译(如果是运行时构建,还可能做了模板字符串到render函数的编译),生成了render函数,但还没调用它生成虚拟DOM,也没把真实DOM插入页面。这个阶段在实际开发里用得很少,因为你能做的事,在created里基本也能做。唯一的区别是,beforeMount时你可以对data做最后一次修改,且这次修改不会再触发额外的更新流程,因为此时组件还没进行首次渲染。
mounted就是重头戏了。从这个钩子开始,this.$el不再是undefined,真实DOM也已经挂载到了页面上。你可以放心地操作DOM了:用querySelector找节点、初始化某个依赖DOM的插件、给canvas画图、绑定第三方库的事件等等。
我踩过最深的一个坑,就是在mounted里直接操作子组件的DOM。你以为父组件mounted了,子组件就一定渲染完了?不一定。父组件的mounted触发时,子组件可能还在挂载过程中。如果你在父组件mounted里通过this.$refs.child.$el去拿子组件DOM,可能拿到的是undefined。解决办法是等子组件自己的mounted触发了再操作,或者用this.$nextTick包一层,但nextTick也不能100%保证子组件挂载完成。这个父子组件生命周期顺序的问题,我后面会详细讲。
另外注意,mounted只在组件挂载完成后触发一次。如果组件是通过v-if动态创建/销毁的,每次创建都会重新走一遍beforeCreate到mounted的流程。如果组件被keep-alive缓存了,首次挂载走正常流程,后续切换则走activated钩子,而不是再次mounted。
2.3 更新阶段:beforeUpdate与updated
当组件依赖的响应式数据发生变化时,会触发更新流程。beforeUpdate在数据变化之后、DOM重新渲染之前触发。一个典型的使用场景是:获取数据变化之前的DOM状态。比如你要做一个列表项的展开/收起动画,需要在数据变化前记录元素当前的高度,然后在更新后把它过渡到新高度。在beforeUpdate里读取旧DOM状态是安全的,因为此时DOM还没被改动。
updated在DOM更新完成后触发。在updated里,你可以拿到更新后的DOM了。但要注意,updated会在组件每次数据变化导致DOM更新后都触发,是一个非常高频的钩子。你要是往updated里塞一个重操作,比如大量数据的计算、复杂的DOM遍历、或者直接修改响应式数据(这会导致再次触发更新,形成死循环),页面性能分分钟崩给你看。
我在一个老项目里见过这样的代码:updated里调用了一个接口,然后根据接口返回值再改data。这个逻辑本身的出发点可能是"每次数据更新后同步服务端数据",但结果就是接口被调了无数次,因为接口返回后改data,又会触发updated,形成了"更新→请求→更新→请求"的死循环。后来改成了watch加防抖,才把性能问题解决掉。所以我的建议是:能不用updated就不用updated,大多数更新后的逻辑,用watch+nextTick替代会更可控。
2.4 销毁阶段:beforeDestroy与destroyed
Vue 2里的beforeDestroy、destroyed,在Vue 3中改名为beforeUnmount和unmounted。名字变了,作用不变。beforeDestroy/beforeUnmount在组件销毁之前触发,此时组件实例还能正常使用,你的清理工作应该在这个阶段完成。
最常见的清理动作包括:清除setInterval/setTimeout定时器、解绑window或document上绑定的事件、关闭WebSocket连接、销毁第三方图表实例(比如ECharts实例的dispose)、取消未完成的异步请求。这些事如果不做,轻则内存泄漏,重则组件虽然销毁了,但定时器还在执行回调,回调里访问已销毁组件的data,导致报错。
这里我特别想强调一个细节:很多人只在beforeDestroy里清理定时器,但忽略了事件监听的清理。如果你在mounted里写了window.addEventListener('resize', this.handleResize),那在beforeDestroy里一定要写window.removeEventListener('resize', this.handleResize)。否则组件销毁后,窗口尺寸一变,回调照样执行,而且由于回调持有组件实例的引用,整个组件占用的内存都无法被垃圾回收。这类问题排查起来特别隐蔽,页面上反应是"越用越卡",内存监控里能看到内存只增不减。我后来定了一条团队规范:所有在组件里绑定的全局事件和定时器,必须在beforeDestroy里对称解绑;凡是addEventListener和removeEventListener成对出现,setInterval和clearInterval成对出现。审核代码时专门查这条。
destroyed/unmounted触发时,组件实例已经被销毁了,此时再访问this.$el会得到undefined,做任何操作都没有意义,实际开发中用到的频率很低,大多就是做个标记或打日志。
3. Vue 2与Vue 3生命周期的关键差异
3.1 命名的变化与替代关系
Vue 3除了把beforeDestroy改名为beforeUnmount、destroyed改名为unmounted之外,还引入了一套全新的Composition API。在Composition API里,生命周期钩子的写法完全变了:不再是在options里配置钩子函数,而是通过导入的API函数在setup里注册。
对应关系如下:
| Vue 2选项式 | Vue 3选项式 | Vue 3组合式 |
|---|---|---|
| beforeCreate | beforeCreate | 不需要(setup就是) |
| created | created | 不需要(setup就是) |
| beforeMount | beforeMount | onBeforeMount |
| mounted | mounted | onMounted |
| beforeUpdate | beforeUpdate | onBeforeUpdate |
| updated | updated | onUpdated |
| beforeDestroy | beforeUnmount | onBeforeUnmount |
| destroyed | unmounted | onUnmounted |
| 无 | 无 | onActivated(配合keep-alive) |
| 无 | 无 | onDeactivated(配合keep-alive) |
| 无 | 新增 | onErrorCaptured |
在组合式API里,没有beforeCreate和created的对应函数,因为setup函数的执行时机就在beforeCreate和created之间,你在setup里写的代码天然等同于在created阶段执行。这个设计很巧妙——setup本身就是"创建阶段"的载体,不需要再单独给两个钩子。
3.2 组合式API下的生命周期使用套路
用组合式API写生命周期,最直观的变化是:同一个功能模块的生命周期逻辑,可以物理性地放在一起。在选项式API里,数据请求相关逻辑可能分散在data、created、methods里;在组合式API里,你可以把数据、请求函数、onMounted注册、onBeforeUnmount清理,全部写在一个自定义函数里,可维护性提升了一大截。
比如我写一个倒计时功能,组合式API的写法是这样的:
import { ref, onMounted, onBeforeUnmount } from 'vue' function useCountdown(initialSeconds) { const seconds = ref(initialSeconds) let timer = null const start = () => { if (timer) return timer = setInterval(() => { if (seconds.value <= 0) { clearInterval(timer) timer = null return } seconds.value-- }, 1000) } const stop = () => { if (timer) { clearInterval(timer) timer = null } } // 生命周期逻辑直接封装在这个函数内部 onMounted(start) onBeforeUnmount(stop) return { seconds, start, stop } }组件里只需要调用useCountdown(60),就能拿到秒钟响应式数据和启动/停止方法,而且定时器的创建和清理逻辑被封装在一起了,不会出现"创建逻辑写在组件A里、清理逻辑忘在组件B里"的尴尬。这是组合式API在生命周期管理上最大的优势。
3.3 组合式API与选项式API的选用建议
我在实际项目里的经验是:新项目优先使用组合式API,老项目如果是Vue 2,继续用选项式API没毛病,不要强行引入Vue 3的组合式API(虽然有@vue/composition-api插件,但属于过渡方案)。Vue 3项目里,如果业务逻辑比较简单,用选项式API写也完全没问题,Vue 3并没有取消选项式API,两种写法可以共存于同一个组件中。我当时在组内推组合式API,不是因为跟风,而是因为项目里页面逻辑越来越复杂,选项式API在代码组织上已经开始"失控"了——一个页面可能有十几个data字段、七八个methods、三四个watch,代码是散的,读起来非常累。组合式API把相关逻辑聚拢在一起,至少阅读成本低了一大截。
还有一个小技巧:在Vue 3组合式API里,onMounted注册的函数可以有多个,不像选项式API里mounted只能写一个。你可以在useA函数里注册一个onMounted,在useB函数里再注册另一个onMounted,它们按注册顺序依次执行。这个能力让多个组合式函数可以各自管理自己的生命周期,互不干扰。如果选项式API里你要写两个mounted逻辑,只能合并到一个方法里,还得小心别互相影响。
4. 生命周期在真实项目中的实战用法
4.1 数据请求:created还是mounted?
这个问题堪称前端面试经典题。先说结论:没有绝对的孰优孰劣,但多数场景下created更合适。理由我前面已经说了,created阶段数据响应式已可用,请求发出去之后,等数据回来赋值给data,即使DOM还没渲染完,Vue也能在渲染时直接拿到最新数据。在mounted里发请求则要等DOM挂载完成,多一段等待时间。
但有一个场景例外:如果你在created里发请求,且请求参数依赖某个DOM元素上的尺寸或位置信息(比如发送当前元素在页面中的坐标),那就必须在mounted里发,因为created时DOM还不存在。不过这种情况比较少见,更常见的做法是先渲染完DOM,再在mounted里通过ref获取元素信息后发请求。
另外注意,mounted里的请求只会发一次,后续组件更新不会重新触发。如果你希望"每次进入页面都拉取最新数据",配合路由守卫或watch路由参数会更合适,而不是依赖mounted。比如列表页面根据URL里的page参数请求数据,用watch('$route.params.page', fetchData)就能在参数变化时自动请求,mounted只在首次进入时执行一次。
4.2 事件监听与定时器清理的实际案例
我讲一个真实翻车现场。之前做直播IM聊天室,每条消息组件里有个5秒的自动消失动画。第一次实现时,用setTimeout在mounted里设置,动画结束后把组件从列表里移除。结果用户快速切换聊天室的场景下,组件报了一堆"Script error"之类的错,排查了很久才发现是定时器回调里访问了一个已经被销毁的组件实例的method。修复方案就是在beforeDestroy里把定时器清掉,同时把"动画结束移除组件"的逻辑改为由父组件通过事件驱动。
类似的问题也出在WebSocket上。比较典型的是:一个组件在mounted里建立了WebSocket连接,但它不是全局唯一的,用户打开多个组件实例时,会建立多个连接,浪费资源。后来我们把WebSocket连接提升到单例模式,在组件的beforeDestroy里做一个引用计数,只有最后一个引用销毁时才真正关闭连接。这些都是生命周期清理逻辑在工程里的实际应用。
还有一个细节:第三方图表库的实例销毁一定要做。ECharts绑定了DOM上的resize事件和内部定时器,如果你不在beforeDestroy里调用chart.dispose(),即使组件销毁了,图表实例仍然存活在内存里,而且监听的事件也不会自动解除。我在老项目里做过一次大排查,光是把图表实例的dispose补上,首页内存占用直接下降了30%以上。别小看这步操作。
4.3 父子组件生命周期执行顺序
这是面试里最容易问晕一个点,也是对实际代码排错非常有用的知识点。看结论:父组件beforeCreate → 父组件created → 父组件beforeMount → 子组件beforeCreate → 子组件created → 子组件beforeMount → 子组件mounted → 父组件mounted → 父组件beforeUpdate → 子组件beforeUpdate → 子组件updated → 父组件updated → 父组件beforeDestroy → 子组件beforeDestroy → 子组件destroyed → 父组件destroyed。
简单记法:挂载阶段是"父先被子"(父会先准备,然后等子都挂完再挂),更新阶段是"父子一起排队"(父的更新会带动子更新,但子更新完父才更新),销毁阶段是"父先被摧毁"(父先进入销毁,然后子销毁,最后父销毁完成)。
实际开发中,这个顺序带来的坑主要在两个场景:
第一个是父组件mounted里拿子组件数据。比如父组件需要在挂载完成后获取所有子组件表单的校验状态。按上面的顺序,父组件mounted触发时,所有子组件都已经mounted了,所以此时访问this.$refs.child.method是安全的。但如果子组件是一个异步组件,情况就不一样了——异步子组件的加载是"父子并行"的,父组件的mounted可能先触发,子组件的mounted还在等待异步加载。这时候在父组件的mounted里访问子组件,拿到的ref可能是undefined。解决方案是在子组件的mounted里通过$emit通知父组件"我准备好了",父组件收到通知后再处理后续逻辑。
第二个是beforeDestroy里父组件访问子组件的DOM。按上面的顺序,父组件触发beforeDestroy时,子组件还没销毁,所以理论上父组件还能访问子组件。但这种访问本身就很危险,因为马上子组件就要销毁了,你在这个阶段做的任何DOM相关操作都可能失效。遇到这种情况,建议重新梳理设计思路,把需要读取的数据提前存好,而不是在销毁阶段临时去拿。
4.4 keep-alive与activated/deactivated
keep-alive是Vue内置的一个特殊组件,它的作用是缓存组件实例,避免组件切换时反复重建。自从用了keep-alive之后,生命周期就不再是"一路走到黑"的线了,而是增加了"缓存后重新激活"的岔路。
当一个组件被keep-alive包裹时,它首次进入会正常走created→mounted,之后切换出去时,不会触发beforeDestroy和destroyed,而是触发deactivated。重新切换回来时,也不会重新走created→mounted,而是触发activated。所以activate就是一个"从缓存中来"的信号,deactivated就是"进入缓存去"的信号。
实际业务里最常见的用法是:列表页滚动位置的保留。一个长列表页,每次切换Tab再切回来,如果重新从头加载,用户体验不好。用keep-alive缓存组件后,滚动位置会自然保留,因为DOM没有被销毁。但如果你需要在每次重新进入时刷新列表数据(比如其他页面修改了数据),就需要在activated里重新请求,而不是在mounted里——因为mounted只在第一次进入时触发一次。
这里有个容易忽略的问题:keep-alive的缓存数量是有限的。不传max属性时,它会无限缓存打开的组件,页面一多,内存占用会一直涨。所以要用include/exclude或者max属性来控制缓存范围。我见过一个项目,把整个首页所有Tab页都包在keep-alive里,还没设置max,结果打开几十个Tab之后,页面明显变卡。后来优化时给keep-alive加了max=5,同时用include只缓存需要保留状态的列表页,问题就解决了。
5. 面试高频追问与避坑指南
5.1 每次面试都会被问的细节追问
问:既然created里能访问data,那created里能访问$refs吗?答案是否定的。created时DOM还没生成,this.$refs是空对象。如果非要访问,只有在mounted之后才行。
问:created里发请求,返回的数据什么时候能渲染到页面上?如果请求是同步返回的(几乎没有),那直接赋值给data即可。异步返回时,Vue会在数据到达后触发更新流程,自动重新渲染相关DOM,不需要你手动调用任何方法。
问:beforeDestroy里还能发请求吗?能发,但不建议。因为组件即将销毁,请求返回后的回调里的this可能已经不可用(Vue 3中访问已卸载组件的数据会告警),容易出问题。如果确实需要在销毁前上报数据,可以用navigator.sendBeacon或把数据存到Vuex/store里异步处理。
问:父组件套子组件,两个组件都想在mounted里发请求,先发谁?按父子组件生命周期顺序,子组件mounted先于父组件mounted,所以子组件的请求会先发出。但这里牵扯到一个"请求并发"的问题,如果你自己用axios等库,需要注意请求之间的依赖关系,必要时用Promise.all控制并发。
问:Vue 3的setup里能拿到this吗?拿不到。setup执行时组件实例已经创建,但还没完全初始化完成,this并不指向组件实例。这也是组合式API和选项式API一个很大的不同——组合式API里建议通过getCurrentInstance()获取组件实例,但这种方法官方并不推荐在业务代码中使用,它更多是给库作者用的。业务里直接用props、emit、refs等方式即可。
5.2 created里拿不到DOM的替代方案
这是一个高频需求:想在组件创建后立即读取或修改某个DOM元素的样式。很多新手在created里写了this.$refs.xxx,发现是undefined,然后又改在mounted里写,却发现偶尔还是会报错。
核心原因就是:created时ref还挂载到对象上;mounted时一般就好了,但因为父组件mounted的时序问题,子组件的ref在父组件mounted里不一定可用。更稳妥的写法是下面这种:
mounted() { this.$nextTick(() => { // 在这里操作DOM,Vue保证DOM已经完成更新 const el = this.$refs.xxx if (el) { el.style.color = 'red' } }) }$nextTick的原理是:在下次DOM更新循环结束后执行延迟回调。它和mounted的区别是,mounted是组件自己的挂载钩子,执行时机在组件挂载后;而nextTick是"等当前这次DOM更新完成后再执行",更精准。我用nextTick解决过很多"操作DOM时机太早"的诡异问题,建议写代码时养成"操作DOM前先确认DOM存在"的习惯。
5.3 接口请求竞态问题
这是生命周期里最隐蔽的一个问题,也是团队里前后端联调最容易扯皮的点。场景是这样的:用户在列表页快速切换筛选条件,每次筛选都触发一个请求。用户第一次选了A条件发请求,请求还没返回,马上选了B条件再发一个请求。如果A的请求比B的请求慢,A返回的数据就会覆盖B的,导致页面显示的数据和当前筛选条件不一致。
这个问题和生命周期本身关系不大,但和"在created里发请求"这个习惯紧密相关。如果你不在created里做任何防抖、竞态处理,就很容易踩坑。我给你一个简单的处理策略:用一个请求序号字段,在每次请求发出前自增,在回调里判断这个序号是不是最新的,不是最新的就丢弃数据:
data() { return { requestSeq: 0, list: [] } }, methods: { async fetchList(params) { const seq = ++this.requestSeq const data = await api.getList(params) // 只有最新一次请求的结果才有效 if (seq === this.requestSeq) { this.list = data } } }这段代码虽然很简单,但在面试里如果能主动讲出"我处理过请求竞态,在created里也防了一手",会是很明显的加分项。
5.4 常见误区清单
我整理了这几年带团队时最常见的生命周期误用和坑,列个清单,你们可以对照自查:
| 误区 | 后果 | 正确做法 |
|---|---|---|
| created里操作DOM | 拿不到节点,报错 | 移到mounted或$nextTick |
| mounted里初始化依赖子组件DOM的插件 | 子组件可能未挂载完成 | 子组件事件通知或使用动态导入机制 |
| updated里修改响应式数据 | 死循环,页面卡死 | 用watch+条件判断替代 |
| 忘记清理定时器/事件监听 | 内存泄漏、报错 | beforeDestroy/beforeUnmount里对称清理 |
| 用mounted代替每次进入页面拉数据 | 数据不刷新 | 配合activated/路由守卫 |
| keep-alive组件里用activated重复初始加载 | 重复请求 | 用缓存标记区分首次/非首次 |
| 销毁前访问$refs | 拿到undefined或空对象 | 提前缓存需要的引用 |
5.5 我在面试里想听到的"加分回答"
最后聊点面试官视角的东西。每当候选人说要"细讲一下Vue的生命周期",我其实不想听他把8个钩子名称挨个背一遍。我想听到的是类似于这样的表达:
"生命周期本质上给了我在组件不同状态下执行逻辑的入口。比如我在created里做数据初始化,因为它不依赖DOM,能提前发请求;我在mounted里做DOM操作,因为这时候节点才真实存在;我在beforeDestroy里做清理,避免内存泄漏。组合式API出现后,我更倾向于把相关功能的生命周期逻辑封装成hook,让管理更内聚。"
这段回答包含三个层次:第一,他知道生命周期各阶段能干什么;第二,他能在实际项目中用对场景;第三,他了解新老API的差异并主动做了更优的选择。这种回答,比背一百遍钩子名称都管用。
说到最后,其实我也不是一开始就懂这些。早几年写Vue,我也在created里拿过DOM、在updated里改过数据、在beforeDestroy里忘记清定时器,翻过很多次车。后来慢慢意识到,生命周期不是面试八股文里死记硬背的考点,而是一套帮助开发者理解组件"什么时候能做什么事"的工具。你把它理解透了,写代码时就会自然地避开很多坑,排查问题的时候心里也更有底。这篇先讲到这里,后面我会再写几篇,把Vue的响应式原理、diff算法、组件通信这些面试重灾区逐个拆开讲。你们如果有在项目里遇到过跟生命周期有关的奇葩问题,也欢迎在评论区聊聊,我看到了会回复。