React 一面:生命周期——从 class 到 Hooks 与常见面试题
React 相关的一面题里,生命周期几乎是必考点。
这一篇不打算按照 API 文档逐个罗列,而是想搞清楚几件事:React 生命周期到底在解决什么问题、class 时代和 Hooks 时代分别怎么表达,以及在面试里哪些题目最容易混淆。
React 生命周期到底在解决什么问题?
先别急着背 componentDidMount、useEffect 这些名字,可以先把组件想象成这样一个过程:
- 初次挂载到页面上(mount)
- 随着 props/state 变化不断更新(update)
- 最后从页面上被移除(unmount)
React 提供的各种生命周期钩子,本质上就是在这三个阶段的 关键时间点 给你一个机会:
- 在组件第一次出现时做初始化工作
- 比如:发起首个请求、订阅事件、测量 DOM。
- 在依赖变化时做同步/副作用更新
- 比如:根据新的 props 拉取数据、同步外部系统状态。
- 在组件销毁前做清理
- 比如:移除事件监听、清理定时器、取消请求。
不管是 class 组件还是函数组件,只是表达方式变了,解决的问题其实一直是这三类。
class 组件视角:生命周期时间轴长什么样?
如果用最常见的 class 组件来画一条「时间轴」,大致是这样几段:
- 挂载阶段(mount)
constructorcomponentDidMount
- 更新阶段(update)
componentDidUpdate
- 卸载阶段(unmount)
componentWillUnmount
在面试里,常问的几个点可以这样理解:
constructor- 更偏向于初始化 state / 绑定方法,不建议在这里做副作用(如请求、DOM 操作)。
componentDidMount- 组件第一次真实挂到 DOM 上后调用,适合:发起网络请求、订阅事件、操作真实 DOM。
componentDidUpdate- 每次更新后触发,适合:根据 props/state 变化去同步外部状态,但要注意条件判断,否则非常容易死循环。
componentWillUnmount- 组件从 DOM 上移除前调用,适合:清理定时器、解绑事件监听、取消请求等。
面试常见问法通常不是「你能背出几个生命周期吗」,而是:
- 「如果要在组件销毁前清理一个订阅,放在哪个生命周期合适?」
- 「如果要在 props 更新后根据新值拉一次数据,你会用哪个生命周期?怎么避免多余请求?」
这些问题本质上都是在考:你能不能把业务动作放在正确的时间点上,并控制好触发频率。
Hooks 视角:生命周期是怎么被拆解的?
函数组件出来之后,React 官方更推荐用 Hooks,这时候「生命周期」不再是一连串命名 API,而是通过 useEffect 这种钩子来表达「什么时候做什么」。
可以把 useEffect 理解成三个组合在一起的东西:
- 依赖列表(依赖谁的变化)
- 副作用函数(变化后要做什么)
- 清理函数(组件卸载或依赖变化前要做什么)
一个典型模式是:
1useEffect(() => {
2 // 副作用逻辑:如发请求、订阅事件等
3
4 return () => {
5 // 清理逻辑:如取消订阅、清理定时器等
6 };
7}, [dep1, dep2]);
和 class 生命周期对照来看,可以大致这样映射:
- 只在挂载时执行一次的逻辑
- class:
componentDidMount - Hooks:
useEffect(() => { ... }, [])
- class:
- 在依赖变化时执行的逻辑
- class:
componentDidUpdate+ 手动判断prevProps/prevState - Hooks:
useEffect(() => { ... }, [dep])
- class:
- 在卸载前执行清理逻辑
- class:
componentWillUnmount - Hooks:
useEffect返回的清理函数
- class:
真正的差异在于:Hooks 把不同副作用拆成多个 useEffect,每个只关心自己的一小块逻辑,而不是在一个生命周期函数里塞很多 if/else。
几个高频面试题:生命周期怎么答更清楚?
结合一下面试里的常见问法,可以列出几个高频考点,并给出更清晰的答题思路。
1. 「如何在 React 组件里发起请求?放在哪个生命周期比较合适?」
答题思路:
- 先区分 挂载时请求一次 和 依赖变化时重复请求。
- 再分别给出 class 和 Hooks 的写法。
可以整理成这样:
- 挂载时请求一次:
- class:在
componentDidMount里发请求。 - Hooks:
useEffect(() => { fetchData(); }, [])。
- class:在
- 依赖变化时请求:
- class:在
componentDidUpdate里对比prevProps/prevState,当某些字段变化时再请求。 - Hooks:
useEffect(() => { fetchData(); }, [someKey])。
- class:在
顺带可以提一句:不要在渲染过程中直接发请求(例如在函数组件体或 render 里),容易造成重复调用或无法控制时机。
2. 「如何在组件销毁时做清理?比如解绑事件、清除定时器」
这里本质考的是:你有没有意识到副作用需要成对出现——订阅 + 取消订阅。
- class 写法:
- 订阅:
componentDidMount - 清理:
componentWillUnmount
- 订阅:
- Hooks 写法:
- 订阅:
useEffect副作用部分 - 清理:
useEffect返回的函数
- 订阅:
答题时可以用一个简短例子描述逻辑,而不需要写完整代码:
- 「挂载时在
componentDidMount里绑定事件,卸载前在componentWillUnmount里解除绑定。」 - 「用 Hooks 的话,在
useEffect里绑定,在返回的清理函数里解绑。」
3. 「为什么会出现 useEffect 的依赖数组?有哪些常见坑?」
这类问题更多是在考 Hooks 的心智模型,可以从三个点来讲:
- 依赖数组控制的是 什么时候重新执行副作用:
[]:只在挂载时执行一次。[a, b]:当a或b变化时执行。- 省略依赖:每次渲染后都会执行,通常不是你想要的。
- 常见坑:
- 忘记把用到的变量写进依赖数组,导致副作用用的是旧值。
- 把每次 render 都会变化的对象/函数直接放进依赖,导致无限循环。
- 面试里如果聊到这一块,可以顺带提一下:
- 实际项目中可以用
useCallback、useMemo等手段稳定依赖,避免不必要的副作用触发。
- 实际项目中可以用
生命周期相关的「陷阱题」与答题建议
有时候面试不会直接问「某个生命周期叫什么」,而是给一个场景,考你能不能把生命周期和副作用管理串起来。
常见的几种「陷阱」包括:
- 在错误的时机操作 DOM
- 比如在
constructor/ 函数组件体里访问 DOM,这时候 DOM 还没挂载。
- 比如在
- 没有清理副作用导致内存泄漏
- 事件监听、定时器、订阅等只绑定不解绑,组件多次挂载卸载后问题会堆积。
- 在更新逻辑里没有条件判断造成死循环
- 在
componentDidUpdate或useEffect里不加条件地 setState,导致无限 re-render。
- 在
答题建议:
- 可以先用一句话给出「正确心智模型」:
- 例如:「我会把组件当成挂载 → 更新 → 卸载三个阶段,在合适的阶段做初始化、同步和清理。」
- 然后给一个简短示例,说明你在实际项目中是怎么用生命周期解决类似问题的。
小结:怎么在面试里讲清楚 React 生命周期?
把这一整块内容压缩成几句面试时能快速说出来的话,大致可以是这样几个点:
- 生命周期本质上就是:在挂载、更新、卸载这三个阶段的关键节点上,让你插入初始化、副作用、清理等逻辑。
- class 时代用一组命名生命周期方法来表达这些时间点,Hooks 时代则用
useEffect等钩子,把副作用拆成更细的颗粒。 - 真正在一面里拉开差距的地方,不是能不能背出名字,而是能不能:
- 把副作用放到合适的阶段;
- 控制好触发频率;
- 在合适的时候做清理,避免泄漏和重复执行。
只要沿着「组件所处的阶段」来思考,而不是死记 API 名字,React 生命周期相关的问题通常都能有条理地讲清楚。