想象你负责安排一位新员工从入职到离职的完整旅程。入职第一天,HR 要先登记身份信息;然后带他参观工区、安排工位,让他正式亮相;工作中他不断接到新任务、调整工作方式;直到离职那天,他要交接项目、归还工牌、注销系统账号。组件的一生,和这段旅程惊人地相似:constructor 是身份登记,render 是首次亮相,componentDidMount 是正式上岗,componentDidUpdate 是成长变化,componentWillUnmount 是离职清理。React 和 Vue3 虽然写法不同,本质都在回答同一个问题——在组件的哪一刻,该做什么事。本文用这场“人生旅程”做主线,带你走完组件从创建、挂载、更新到卸载的全过程,并理清类组件、Hooks 与 Vue3 组合式 API 三种写法的对应关系。

岗位关联

掌握生命周期,你就掌握了在任何前端框架里“在对的时机做对的事”的能力。这是 React/Vue 高阶开发的基石,也是面试中最高频的考点之一。

目录
  1. 可视化演示
  2. 生命周期总览
  3. React挂载阶段
  4. React更新阶段
  5. React卸载阶段
  6. useEffect替代
  7. Vue3组合式钩子
  8. 父子执行顺序
  9. 框架异同对比
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示React类组件和Vue3组合式API的生命周期钩子执行流程,以及父子组件生命周期的由内到外执行顺序。交互切换框架视图。

本节配有交互动效(进入视口后自动加载,约 3KB)。

React类组件生命周期 挂载:Con→Rnd→CDM 更新:SCU→CDU 卸载:CWU清理 componentDidMount() React生命周期分挂载/更新/卸载三阶段

生命周期总览

如果把组件比作一个人,那么它的一生和我们一样,可以粗粗分成三段:出生、成长、告别。出生时要办理户口、登记信息;成长中要读书工作、不断遇见新的人和事;到了告别那天,得把行李收好、把钥匙归还。组件的一生也是这样——它从构造函数中诞生,经历多次渲染更新,最终在页面上被移除。这些关键节点,就是生命周期。

在 React 和 Vue3 中,“生命周期”指的是组件从创建、挂载、更新到卸载的完整过程。框架并不会偷偷替你决定每个时刻该做什么,而是把主动权交给你:在某个节点上,如果你注册了一个函数,框架就会在恰当时机调用它。这种被框架回调的函数,就是钩子函数(hook)。钩子的英文 hook 原意是“挂钩”,像门上的衣帽钩,你挂什么,它就帮你挂住什么。

React 类组件把生命周期拆成一个个方法:constructor、render、componentDidMount、componentDidUpdate、componentWillUnmount。Vue3 组合式 API 则用 setup 加 onMounted、onUpdated、onUnmounted 等函数。虽然名字不同,但都在回答同一件事:挂载时做什么、更新时做什么、卸载前做什么。下面的公式概括了组件一生的主航道。

所以,学生命周期不用背一张大表格,只记住人生三大阶段就抓住了主线:出生办手续、成长做调整、告别做清理。接下来我们逐一走进这三大阶段,看看 React 和 Vue3 各自是怎么安排这场人生旅程的。

Lifecycle=Mount⏟创建挂载→Update⏟更新变化→Unmount⏟卸载销毁\displaystyle \text{Lifecycle} = \underbrace{\text{Mount}}_{\text{创建挂载}} \to \underbrace{\text{Update}}_{\text{更新变化}} \to \underbrace{\text{Unmount}}_{\text{卸载销毁}}
组件生命周期 = 挂载(出生)→ 更新(成长)→ 卸载(告别),所有钩子函数都分布在这条主航道上。
  1. Lifecycle(生命周期)组件从创建到销毁的完整过程,是框架管理组件状态的底层框架。
    Lifecycle:组件的一生\displaystyle \text{Lifecycle}: \text{组件的一生}
  2. Mount(挂载)组件首次被创建并插入真实 DOM 的阶段,类似人生“出生登记”。
    Mount:创建并插入 DOM\displaystyle \text{Mount}: \text{创建并插入 DOM}
  3. Update(更新)props 或 state 变化后组件重新渲染的阶段,类似成长中不断调整自己。
    Update:重新渲染与响应\displaystyle \text{Update}: \text{重新渲染与响应}
  4. Unmount(卸载)组件从 DOM 中移除、销毁前的阶段,类似告别前的行李整理。
    Unmount:移除并清理资源\displaystyle \text{Unmount}: \text{移除并清理资源}

React挂载阶段

一个人生命中除了生日,最重要的日子大概就是入职第一天。这一天要做的事格外多:HR 登记你的基础信息、你拿到工牌和电脑、被同事领着认路。组件也一样,它的“入职第一天”就是挂载阶段,从 constructor 到 render 再到 componentDidMount,一步都不能乱。

constructor 相当于身份登记:在这里初始化 state、把方法绑定到组件实例上,为接下来的生活准备好“行李”。render 则像打印工牌:它根据当前的 props 和 state,计算出组件长什么样,返回一份“描述图”(虚拟 DOM)。注意,render 必须是个“老实人”——只负责描述,绝不能偷偷做修改数据的动作。

等 render 把描述图交给框架,框架把真实 DOM 插入页面后,componentDidMount 才会被调用。这是组件“正式上岗”的信号:此时 DOM 已经真实存在,你可以放心地在这里发起网络请求、订阅事件、操作第三方库。之所以把请求放在这里而不是 render 里,是因为 render 每次更新都会执行,而 didMount 整个生命周期只执行一次。

挂载阶段就像入职第一天:constructor 登记身份、render 打印第一版简历、componentDidMount 正式上岗。搞清这三步,你就掌握了组件“出生”的全过程。

MountReact=constructor⇒render⇒componentDidMount\displaystyle \text{Mount}_{\text{React}} = \text{constructor} \Rightarrow \text{render} \Rightarrow \text{componentDidMount}
React 挂载三连:构造函数初始化 → 渲染函数输出 → 挂载完成回调,顺序是固定的,不可颠倒。
  1. constructor(构造函数)组件实例创建时第一个执行的函数,在这里初始化 state 和绑定方法,是“身份登记”环节。
    constructor:初始化 state、绑定事件方法\displaystyle \text{constructor}: \text{初始化 state、绑定事件方法}
  2. render(渲染函数)根据 props 和 state 输出虚拟 DOM 描述,必须保持纯净、不产生副作用。
    render:props+state→vNode\displaystyle \text{render}: \text{props} + \text{state} \to \text{vNode}
  3. componentDidMount(挂载完成)真实 DOM 插入页面后触发,适合发起网络请求、订阅事件等副作用。
    componentDidMount:DOM 就绪⇒执行副作用\displaystyle \text{componentDidMount}: \text{DOM 就绪} \Rightarrow \text{执行副作用}

React更新阶段

人生不止入职那一天。进入公司后,你每天都会接到新任务、学到新技能,也会因为项目调整而改变工作方式。组件同样会“成长”,只要 props 或 state 发生变化,它就要重新渲染一遍,这个过程就是更新阶段。

更新的第一站是 shouldComponentUpdate。这个名字直译是“应该更新组件吗”,就像一个冷静的顾问在你面前问:这次变化真的需要我大动干戈、重新画一遍吗?如果数据和上次完全一样,回答 false 就能跳过这次渲染,省下宝贵的性能。

如果决定继续,render 会再次执行,根据最新数据生成新的描述图;随后 componentDidUpdate 在更新完成后被调用。在这里你常常需要对比新旧 props,判断是哪个数据引起了变化,再决定要不要做对应的副作用——比如数据筛选、图表重绘。值得注意的是,didUpdate 里直接用 setState 极容易造成死循环,务必加上条件判断。

更新阶段就是“成长的日常”:变化来了,先问值不值得重画,决定画了就好好画,画完再检查这次变化带来了什么。三步走,组件的成长之路就不会乱。

UpdateReact=shouldComponentUpdate⇒render⇒componentDidUpdate\displaystyle \text{Update}_{\text{React}} = \text{shouldComponentUpdate} \Rightarrow \text{render} \Rightarrow \text{componentDidUpdate}
更新三步走:先问值不值得重画,决定画了就老老实实重画,画完再检查变化带来了什么影响。
  1. shouldComponentUpdate(是否更新)在重新渲染前询问是否有必要,返回 false 可跳过本次渲染,是性能优化的闸门。
    shouldComponentUpdate:B⇒继续 or 跳过\displaystyle \text{shouldComponentUpdate}: \mathbb{B} \Rightarrow \text{继续 or 跳过}
  2. render(重新渲染)用最新的 props 和 state 重新生成虚拟 DOM 描述。
    render′:props′+state′→vNode′\displaystyle \text{render}': \text{props}' + \text{state}' \to \text{vNode}'
  3. componentDidUpdate(更新完成)DOM 更新完毕后触发,可对比新旧值执行后续逻辑。
    componentDidUpdate(prevProps,prevState)\displaystyle \text{componentDidUpdate}(\text{prevProps}, \text{prevState})

React卸载阶段

有相聚就有告别。离职那天,你要归还工牌、注销账号、交接手头项目。组件的最后一天就是卸载阶段,只有一场重头戏:componentWillUnmount。这是页面把组件移除前,最后一次给你打电话的机会。

很多人以为组件没了浏览器会自动收拾一切,其实不然。你在挂载阶段订阅的全局事件、启动的定时器、建立的 WebSocket 连接,都不会因为组件消失而自动消失——它们像离职后还在自动扣费的会员卡,继续消耗内存、触发回调。

所以 componentWillUnmount 是卸载前最后的清理窗口:clearInterval 清掉定时器、removeEventListener 摘掉监听、取消未完成的网络请求。错过这一步,轻则内存泄漏,重则出现“组件已卸载但回调还在改状态”的报错。在函数组件中,useEffect 返回的清理函数正是对应这一步。

如果把卸载阶段比作退租,componentWillUnmount 就是退租前的检查清单——关水、断电、还钥匙。每清理一项,内存就少一分负担,页面就多一分流畅。

UnmountReact=componentWillUnmount:clean(timers,listeners,subscriptions)\displaystyle \text{Unmount}_{\text{React}} = \text{componentWillUnmount}: \text{clean}(\text{timers}, \text{listeners}, \text{subscriptions})
卸载钩子是组件销毁前唯一的兜底机会,负责清理定时器、监听器、订阅三类外部资源。
  1. componentWillUnmount(卸载前钩子)组件从 DOM 移除前触发的最后一个钩子,整个生命周期只执行一次。
    componentWillUnmount:组件销毁前兜底\displaystyle \text{componentWillUnmount}: \text{组件销毁前兜底}
  2. 清理定时器clearInterval 停止后台定时任务,防止组件销毁后代码仍在执行。
    clearInterval(timerId)\displaystyle \text{clearInterval}(\text{timerId})
  3. 移除监听与订阅removeEventListener 或取消订阅,切断组件与外部世界的联系。
    removeEventListener(type,handler)\displaystyle \text{removeEventListener}(\text{type}, \text{handler})

useEffect替代

从 React 16.8 开始,生活有了新方式:函数组件不再需要 class,也能拥有生命周期。秘诀是一张“生活作息表”useEffect。它不像 class 时代把不同阶段拆成不同方法,而是用一套统一机制覆盖挂载、更新、清理三件事。

useEffect(fn, deps) 接收两个参数:一个函数 fn(你要做的事),一个依赖数组 deps(做这件事的条件)。每次渲染完成后,框架都会检查 deps:里面的值如果和上次不一样,就执行 fn;如果 fn 返回一个清理函数,就在下一次执行前或卸载前先调用它。

于是,传空数组 [] 就等于“只在挂载后做一次,卸载前做清理”,完美模拟 componentDidMount 加 componentWillUnmount;传 [count] 则意味着 count 变化时执行,类似 componentDidUpdate。这张作息表的精髓在于:你的生活规律(依赖)写得越诚实,组件的状态就越安全。

生活作息表把三个独立阀门合成一个旋钮:只要调对依赖数组,挂载、更新、清理尽在掌握。这也是目前 React 官方推荐的函数组件写法。

useEffect(f,deps)={f on mount,deps=[]f when deps change,deps=[d1,d2,… ]cleanup on unmount,f returns cleanup\displaystyle \text{useEffect}(f, \text{deps}) =\begin{cases}f \text{ on mount}, & \text{deps} = [] \\f \text{ when deps change}, & \text{deps} = [d_1, d_2, \dots] \\\text{cleanup on unmount}, & f \text{ returns cleanup}\end{cases}
useEffect 的三种用法:空数组只挂载执行,非空数组依赖变化时执行,返回函数在卸载前做清理。
  1. 挂载后执行依赖数组为空时,仅在组件首次挂载后运行一次 fn。
    deps=[]⇒f 只执行一次\displaystyle \text{deps} = [] \Rightarrow f \text{ 只执行一次}
  2. 依赖变化时执行数组里填的变量任一发生变化,fn 就重新运行,实现更新监控。
    deps=[d1,d2,… ]⇒f 在 di 变化后执行\displaystyle \text{deps} = [d_1, d_2, \dots] \Rightarrow f \text{ 在 } d_i \text{ 变化后执行}
  3. 卸载前清理fn 返回的函数会在下次 effect 前和组件卸载前被调用,是清理副作用的出口。
    f→cleanup⇒卸载时调用 cleanup\displaystyle f \to \text{cleanup} \Rightarrow \text{卸载时调用 cleanup}

Vue3组合式钩子

如果说 React 的函数组件是把手表揉进了一个函数,那么 Vue3 的组合式 API 则像是搬进一间“组合式公寓”——公寓不按房间分功能区,而是按你的需求自由组装:鞋柜放门口、书桌靠窗、健身器挨着阳台,一切都听从 setup 的统一调度。

setup 是组合式 API 的入口,相当于公寓的“总设计师”。组件创建时,setup 最先执行,负责创建响应式数据、把多个功能的逻辑组织在一起。它本身就是一个特殊的生命周期节点,执行的时机比 onMounted 更早。

在 setup 内部,你可以随时调用 onMounted(挂载完成)、onUpdated(更新完成)、onUnmounted(卸载前)来注册自己的钩子函数。注意,这些 onXxx 必须在 setup 期间同步调用,框架才能把它们登记到当前组件上。它们与 React 的生命周期一一对应:onMounted 对应 componentDidMount,onUnmounted 对应 componentWillUnmount。

组合式公寓的居住体验就是:不用再跑到“选项式”的各个房间里找对应方法,所有生命周期钩子都像家具一样在 setup 里按需组装。想挂载时做事?onMounted 安排;想卸载前清理?onUnmounted 收尾。

Vue3lifecycle=setup→onMounted(fm)→onUpdated(fu)→onUnmounted(fc)\displaystyle \text{Vue3}_{\text{lifecycle}} = \text{setup} \to \text{onMounted}(f_m) \to \text{onUpdated}(f_u) \to \text{onUnmounted}(f_c)
Vue3 组合式生命周期 = setup 总入口 + onMounted + onUpdated + onUnmounted,所有钩子在 setup 内按需注册。
  1. setup(组合式入口)组件初始化时最先执行,创建响应式状态并注册钩子。
    setup:创建响应式数据、注册钩子\displaystyle \text{setup}: \text{创建响应式数据、注册钩子}
  2. onMounted(挂载完成)组件挂载完成后回调,适合请求数据、操作 DOM。
    onMounted(fm):DOM 就绪后调用 fm\displaystyle \text{onMounted}(f_m): \text{DOM 就绪后调用 } f_m
  3. onUpdated(更新完成)响应式数据变化导致重新渲染后触发。
    onUpdated(fu):重渲染后调用 fu\displaystyle \text{onUpdated}(f_u): \text{重渲染后调用 } f_u
  4. onUnmounted(卸载清理)组件卸载前调用,用来清理定时器与监听。
    onUnmounted(fc):卸载前调用 fc\displaystyle \text{onUnmounted}(f_c): \text{卸载前调用 } f_c

父子执行顺序

人生从来不是独角戏。我们生活在一个个家庭里,长辈与晚辈的先后次序,决定了家庭运转的节奏。组件也一样,父组件和子组件嵌套时,它们的生命周期钩子会按一套严格的“家庭规则”依次执行。

挂载时,父组件先进入 render——它要先把“户型图”画出来,中途发现需要子组件,就暂时停下来,让子组件从 constructor 开始完整地走完自己的挂载流程。子组件宣告挂载完成后,父组件才执行自己的 componentDidMount。这套顺序可以用一句话记:父先开始,子先完成,父后宣布。

更新阶段类似:父组件的 render 先执行,子组件随后重新渲染并完成 didUpdate,父组件的 didUpdate 最后落定。而卸载阶段则相反,子组件先 componentWillUnmount,父组件再卸载——先拆屋顶再拆承重墙,才不会砸到人。Vue3 中同样遵循“父 beforeMount → 子 mounted → 父 mounted”和“子 unmounted → 父 unmounted”的先后。

记住这条家庭规则,你就能准确预测任意嵌套组件中钩子的触发顺序,在排查“为什么子组件先请求”这类问题时不再两眼一抹黑。

Order={Mount:Prender→[Call]→PdidMountUpdate:Prender→[Cupdate]→PdidUpdateUnmount:CwillUnmount→PwillUnmount\displaystyle \text{Order} =\begin{cases}\text{Mount}: P_{\text{render}} \to [C_{\text{all}}] \to P_{\text{didMount}} \\\text{Update}: P_{\text{render}} \to [C_{\text{update}}] \to P_{\text{didUpdate}} \\\text{Unmount}: C_{\text{willUnmount}} \to P_{\text{willUnmount}}\end{cases}
家族纪律:挂载时父先开始、子先完成;卸载时子先离开、父再收尾;更新时父导子随。
  1. 挂载顺序父组件 render 启动渲染,子组件完整挂载完成后,父组件才执行 componentDidMount。
    Prender→[Cconstructor→CdidMount]→PdidMount\displaystyle P_{\text{render}} \to [C_{\text{constructor}} \to C_{\text{didMount}}] \to P_{\text{didMount}}
  2. 更新顺序父组件先重新渲染,子组件随后更新,父组件最后完成更新回调。
    Prender→[Crender→CdidUpdate]→PdidUpdate\displaystyle P_{\text{render}} \to [C_{\text{render}} \to C_{\text{didUpdate}}] \to P_{\text{didUpdate}}
  3. 卸载顺序子组件先清理自己的资源,父组件再执行卸载,防止共享资源被提前释放。
    CwillUnmount→PwillUnmount\displaystyle C_{\text{willUnmount}} \to P_{\text{willUnmount}}

框架异同对比

同一个人,可以过四种不同的生活:住酒店有酒店的服务流程,住公寓有公寓的管理规约。组件的生命周期在不同框架里也有四种“活法”,但背后的三阶段主线完全相同。

React 类组件把钩子写成方法:constructor、componentDidMount、componentDidUpdate、componentWillUnmount;React 函数组件用 useEffect 加依赖数组;Vue3 选项式保留 mounted、updated、beforeUnmount 等选项;Vue3 组合式则在 setup 里调用 onMounted、onUpdated、onUnmounted。四种写法、四套名字,映射到同一个生命周期坐标轴。

选择哪种写法,取决于项目和团队:老项目用类组件或选项式,迁移成本低;新项目推荐函数组件加 Hooks 或组合式 API,逻辑复用更强。但无论选哪种,理解“挂载做初始化、更新做响应、卸载做清理”这个底层模型,才能在任何框架中游刃有余。

四种活法就像同一座城市里不同的通勤方式:公交、地铁、骑行、步行。路线不同,目的地永远是同一个——把组件的每一个阶段管理好。作为开发者,挑一种适合当前项目的,同时理解其他几种,就能随时迁移不迷路。

Class:constructor→didMount→didUpdate→willUnmountHooks:useEffect(f,[])→useEffect(f,deps)→cleanupVue3:onMounted→onUpdated→onUnmounted\displaystyle \begin{aligned}\text{Class} &: \text{constructor} \to \text{didMount} \to \text{didUpdate} \to \text{willUnmount} \\\text{Hooks} &: \text{useEffect}(f, []) \to \text{useEffect}(f, \text{deps}) \to \text{cleanup} \\\text{Vue3} &: \text{onMounted} \to \text{onUpdated} \to \text{onUnmounted}\end{aligned}
三条路线本质相同:类组件用独立方法、Hooks 用依赖数组、Vue3 用 onXxx 注册,都是挂载→更新→卸载。
  1. 类组件路线每个阶段对应一个独立方法,结构清晰但逻辑复用较难。
    Class→独立生命周期方法\displaystyle \text{Class} \to \text{独立生命周期方法}
  2. Hooks 路线统一为 useEffect 加依赖数组,按逻辑组织而非按阶段组织。
    Hooks→useEffect(f,deps)\displaystyle \text{Hooks} \to \text{useEffect}(f, \text{deps})
  3. Vue3 路线onXxx 钩子在 setup 中注册,组合式地组织逻辑。
    Vue3→setup+onXxx\displaystyle \text{Vue3} \to \text{setup} + \text{onXxx}

AI 应用场景

智能客服埋点与用户行为分析

阶段:数据采集与特征工程

解决的问题:大模型驱动的智能客服需要高质量的用户行为数据来训练话术。传统埋点把上报代码散落在各处,容易重复上报或漏报。用生命周期钩子把“会话开始/结束”集中管理:挂载时上报开始,卸载时上报结束,数据质量大幅提升。就像给人生旅程的两端各盖一个章,起点终点清清楚楚。

具体案例:某银行 AI 客服机器人页面,开发者在 onMounted 时上报“会话开始 + 渠道来源”,在 onUnmounted 时上报“会话结束 + 停留时长”。这些数据进入大模型训练管道,用于优化客服话术和意图识别准确率。

AI 实时质检大屏的 WebSocket 连接管理

阶段:在线推理服务的实时数据接入

解决的问题:AI 质检大屏的每个监控卡片组件都需要和推理服务保持 WebSocket 长连接,若不在卸载时断开,切换页面会导致连接堆积、内存泄漏。生命周期卸载钩子统一负责断开连接,保证资源随组件一起释放。

具体案例:某工厂部署 AI 瑕疵检测系统,每个机台是一个组件。组件挂载时建立连接订阅推理结果,卸载时断开连接、取消订阅。即使一屏几十个组件来回切换,前端内存依旧平稳,推理结果实时刷新不卡顿。

模型渲染性能监控

阶段:应用质量监控与优化

解决的问题:AI 应用往往携带重型模型库,渲染耗时直接影响用户体验。在 componentDidUpdate 里对比时间戳,可以精确算出每次更新的渲染耗时,发现异常及时上报给监控平台。

具体案例:某人脸识别 H5 应用,开发者在 componentDidUpdate 中计算前后渲染时间差,耗时超过 200ms 就上报。上线后提前定位到列表未加 key 导致的全量重渲染问题,修复后首屏时间下降 40%。

Python 演示

python69 行
class SimulatedComponent:    """用 Python 类模拟组件生命周期三个阶段"""     def __init__(self, name):        self.name = name        self.props = {"count": 0}        self.mounted = False        print("[%s] constructor 出生登记" % self.name)     def render(self):        print("[%s] render 绘制界面 count=%s" % (self.name, self.props["count"]))     def component_did_mount(self):        self.mounted = True        print("[%s] componentDidMount 正式上岗" % self.name)     def component_did_update(self):        print("[%s] componentDidUpdate 更新完成" % self.name)     def component_will_unmount(self):        print("[%s] componentWillUnmount 卸载前清理" % self.name)     def mount(self):        self.render()        self.component_did_mount()     def set_props(self, key, value):        self.props[key] = value        print("[%s] props 变化 %s=%s" % (self.name, key, value))        self.render()        self.component_did_update()     def unmount(self):        self.component_will_unmount()        self.mounted = False        print("[%s] 已卸载" % self.name)  class ParentComponent(SimulatedComponent):    def mount(self):        self.render()        self.child = ChildComponent("Child")        self.child.mount()        self.component_did_mount()     def set_props(self, key, value):        self.props[key] = value        print("[%s] 父组件 props 变化, 重新渲染" % self.name)        self.render()        self.child.render()        self.child.component_did_update()        self.component_did_update()     def unmount(self):        self.child.unmount()        self.component_will_unmount()        print("[%s] 父组件已卸载" % self.name)  class ChildComponent(SimulatedComponent):    pass  if __name__ == "__main__":    app = ParentComponent("App")    app.mount()    app.set_props("count", 1)    app.set_props("count", 2)    app.unmount()
目的:用纯 Python 模拟 React 类组件生命周期,直观演示挂载、更新、卸载三个阶段以及父子组件的执行顺序。
  1. class SimulatedComponent:定义一个“模拟组件”基类,用 Python 类复刻 React 生命周期
  2. def render(self):对应 React 的 render,只做描述不产生副作用
  3. def component_did_mount(self):对应 componentDidMount,挂载完成后执行
  4. def set_props(self, key, value):模拟 props 变化触发更新流程
  5. class ParentComponent(SimulatedComponent):父组件继承基类并重写挂载顺序
  6. self.child.mount()父组件渲染后创建子组件并让子组件先完成挂载
  7. self.component_will_unmount()卸载前执行清理逻辑,演示先子后父的顺序

常见误区

误区“我直接把数据请求写在 render 函数里不行吗?反正渲染时自然会请求一次,还能省一个钩子。”

正解render 是纯函数,每次更新都会重新执行。直接把请求写进去会导致:请求随每次 setState 重复发出,甚至形成“请求→setState→再请求”的死循环。正确做法是把副作用交给 componentDidMount / useEffect / onMounted。就像你写日记不该在照镜子时顺手写,而应在固定时间坐下来写。

为何常见:render 是最直观的“执行代码的地方”,初学者天然想往里塞逻辑,忽略了它会被反复调用。

误区“反正组件都要销毁了,浏览器会回收内存,定时器和监听器不用管。”

正解浏览器确实会回收 DOM,但定时器、事件监听、WebSocket、全局变量都属于组件外部资源,组件销毁后它们依然存活并持续消耗内存、触发回调。必须像“出门前关煤气”一样,在 componentWillUnmount 或 useEffect 清理函数里主动释放。

为何常见:简单页面看不出泄漏,等频繁切换页面后越来越卡顿,才后知后觉是清理没做。

误区“我把 useEffect 的依赖数组写成空数组,effect 只执行一次,闭包里读到的 props 一定是当前最新的。”

正解[] 只是让 effect 在挂载后执行一次,但闭包捕获的是首次渲染时的 props/state。如果里面有变化的变量,读到的永远是“第一次见面时的样子”,这就是经典的 stale closure 问题。解决方法是把用到的变量补进依赖数组,或用 useRef 保持最新引用。就像揣着一张旧地图去找新路,永远走不对。

为何常见:“只执行一次”的直觉正确,但闭包快照机制很隐蔽,没有踩过坑很难想到。

误区“树是从上往下建的,所以卸载时父组件先走、子组件随后跟上。”

正解React 卸载是自底向上的:子组件先执行 componentWillUnmount,父组件后执行。因为父组件的清理(如释放共享资源)必须保证子组件已经停止工作。就像拆除大楼先拆内部隔断、再拆承重墙,顺序反了会出事故。

为何常见:挂载时“父先开始、子先完成”的直觉,被错误地延续到了卸载阶段。

误区“Vue3 的 setup 里只能写响应式数据,以前那些 mounted、beforeUnmount 方法全没了,生命周期被砍掉了。”

正解生命周期依然存在,只是换成了组合式钩子函数:onMounted、onUpdated、onUnmounted、onBeforeUnmount 等。setup 本身就是“组合式入口”,相当于把生命周期注册从选项对象变成了可自由调用的函数,用起来更灵活,也更容易复用逻辑。

为何常见:选项式到组合式的 API 形态变化太大,让人误以为功能被删减,其实只是换了个房间住。

面试题

Q1. 组件挂载阶段,以下哪个执行顺序是正确的?

单选题 · easy

  • A.constructor → componentDidMount → render
  • B.render → constructor → componentDidMount
  • C.constructor → render → componentDidMount← 正确答案
  • D.componentDidMount → render → constructor

答案:C。挂载三连的顺序是固定的:constructor 先做身份登记(初始化 state),render 输出描述图,最后插入真实 DOM 后触发 componentDidMount。其他选项都打乱了“登记→绘制→上岗”的人生入职流程。

Q2. 组件卸载前,哪个钩子负责最后的清理工作?

单选题 · easy

  • A.render 负责清理未完成的渲染任务
  • B.componentWillUnmount 执行卸载前清理← 正确答案
  • C.componentDidMount 负责挂载后的清理
  • D.shouldComponentUpdate 执行渲染前清理

答案:B。componentWillUnmount 是组件销毁前唯一的清理窗口,负责清定时器、摘监听、断连接。render 是纯描述函数不应有副作用,componentDidMount 是挂载后的钩子,shouldComponentUpdate 是更新前的“闸门”。

Q3. Vue3 组合式 API 中,如何在挂载完成后执行逻辑?

单选题 · mid

  • A.在 setup() 中调用 onMounted 注册回调← 正确答案
  • B.在模板指令中绑定 mounted 回调
  • C.在 store 里全局监听 mounted 事件
  • D.在 props 中传入 mounted 处理函数

答案:A。组合式 API 要求在 setup() 内同步调用 onMounted 注册回调,框架才会把钩子登记到当前组件上。模板指令、store 全局监听、props 传函数都不是 Vue3 提供的生命周期注册方式。

Q4. 关于父子组件挂载顺序,正确的说法是?

单选题 · mid

  • A.父组件 render 后子组件先完成挂载← 正确答案
  • B.子组件渲染完成后父组件才 render
  • C.父子组件完全独立并行执行挂载
  • D.挂载顺序由组件代码量大小决定

答案:A。父组件先 render 发现子组件,暂停等待子组件完整走完 constructor→render→didMount,子组件宣告挂载完成,父组件才执行自己的 componentDidMount。“父先开始、子先完成、父后宣布”。

Q5. useEffect 的依赖数组为 [] 时,effect 会如何执行?

单选题 · mid

  • A.每次渲染完成后都会重新执行
  • B.仅挂载后执行一次且卸载前清理← 正确答案
  • C.依赖数组为空则 effect 永不执行
  • D.props 变化的一瞬间立即执行

答案:B。空数组表示“没有需要追踪的依赖”,effect 只在挂载后执行一次;如果它返回清理函数,则卸载前调用。它不会每次渲染执行,也不会永不执行,更不是同步立即触发。

Q6. Vue3 中与卸载阶段对应的组合式钩子是?

单选题 · mid

  • A.onUnmounted 用于注册卸载前回调← 正确答案
  • B.beforeUnmount 仅存在于选项式写法
  • C.onDestroy 是 Vue3 官方卸载钩子
  • D.onSetup 负责卸载前的状态保存

答案:A。组合式 API 用 onUnmounted 注册卸载前清理逻辑。beforeUnmount 确实是选项式的卸载钩子名,但题目问的是“组合式钩子”,且组合式里对应的是 onBeforeUnmount;onDestroy 是 Vue2 的历史叫法;没有 onSetup 这个钩子。

Q7. 在 componentDidUpdate 中调用 setState 时必须注意什么?

单选题 · hard

  • A.任何情况下都可以安全调用直接更新
  • B.必须加条件判断否则会陷入死循环← 正确答案
  • C.每次更新只能调用一次否则报错
  • D.不能在 componentDidUpdate 中使用 setState

答案:B。componentDidUpdate 里调用 setState 会触发新一轮更新,新一轮更新又会进入 didUpdate,形成死循环。因此必须先对比新旧 props/state,满足条件时才 setState。完全禁止过于绝对,无条件调用则必然出问题。

Q8. 以下哪些操作应该放在 componentWillUnmount 中完成?

多选题 · mid

  • A.清除之前启动的定时器任务← 正确答案
  • B.移除全局对象上的事件监听← 正确答案
  • C.发起新的网络请求获取数据
  • D.更新 state 触发最后一次渲染

答案:AB。卸载前要做的是“归还资源”:清定时器、摘监听、断连接。发起新请求属于挂载阶段的职责,而且卸载后请求返回也无处渲染;卸载时更新 state 违反 React 规范,组件即将销毁不应再触发渲染。

Q9. 关于 useEffect 的以下说法,正确的是?

多选题 · hard

  • A.依赖数组控制 effect 的执行时机← 正确答案
  • B.返回函数用于在卸载前清理资源← 正确答案
  • C.依赖数组为空时每次渲染都执行
  • D.effect 会阻塞浏览器页面渲染

答案:AB。依赖数组是 useEffect 的“开关”:空数组只在挂载后执行,非空数组在依赖变化时执行;返回的 cleanup 函数在下次执行前和卸载前调用。空数组不会每次渲染执行,effect 在渲染完成后异步执行,不会阻塞页面渲染。

Q10. 关于父子组件整个生命周期的全过程,下列说法正确的有?

多选题 · extreme

  • A.子组件先于父组件执行卸载清理← 正确答案
  • B.父组件更新会引发子组件重新渲染← 正确答案
  • C.父组件的挂载完成回调先于子组件执行
  • D.卸载阶段可以安全地异步更新状态

答案:AB。卸载是“先拆屋顶再拆承重墙”,子组件先 willUnmount 父组件后执行,A 正确;父组件重新渲染时子组件必然跟随更新,B 正确;父组件的 componentDidMount 要等子组件全部挂载完成才执行,C 错误;卸载阶段已销毁的组件不应再更新状态,否则会告警甚至内存泄漏,D 错误。