Skip to content

React 文档

导读与全书目录表:见同目录 01-React 入门:简介·特性与设计范式.md 开头。


react 生命周期

函数组件生命周期

::: tip上、下指的是在函数内部的最上方和最下方 :::

  1. 组件初始化:父上 > 父下 > 子上 > 子下 > 子挂载完成 > 子依赖变化后执行 > 父挂载完成 > 父依赖变化后执行
  2. 组件状态变更:父上 > 父下 > 子上 > 子下 > 父依赖变化后执行
  3. 组件销毁:父上 > 父下 > 子组件卸载
  4. 所有生命周期说明
shell
挂载完成:useEffect(() => { 挂载执行... },[])
依赖变化:useEffect(() => { 改变执行... },[count依赖])
卸载完成:useEffect(() => { return () => { 卸载执行... } },[])

类组件生命周期

  1. 组件初始化(4个):constructor getDerivedStateFromProps render componentDidMount
shell
父constructor > 父getDerivedStateFromProps > 父render > 子constructor > 子getDerivedStateFromProps >
子render > 子componentDidMount > 父componentDidMount
  1. 组件状态变更(5个):getDerivedStateFromProps shouldComponentUpdate render getSnapshotBeforeUpdate componentDidUpdate
shell
父getDerivedStateFromProps > 父shouldComponentUpdate > 父render > 子getDerivedStateFromProps > 子shouldComponentUpdate >
子render > 子getSnapshotBeforeUpdate > 父getSnapshotBeforeUpdate > 子componentDidUpdate > 父componentDidUpdate;
  1. 组件销毁(1个):componentWillUnmount

卸载不会像「更新」那样再走一遍 getDerivedStateFromPropsrendergetSnapshotBeforeUpdate 链。常见顺序是 自子向父 依次执行 componentWillUnmount(先深层子组件,再到父组件),用于清理订阅、定时器等。

shell
子componentWillUnmount > > 父componentWillUnmount
  1. 类组件所有生命周期说明
shell
constructor:初始化state,绑定方法,切记不要直接调用 setState()
getDerivedStateFromProps(从 props 获取派生状态):返回一个对象来更新 state,或 null 不更新
render:返回 React 元素、数组、Fragment、Portal、字符串或布尔值
componentDidMount(组件已挂载):组件挂载后立即调用,适合进行网络请求、DOM 操作、订阅等
shouldComponentUpdate(组件是否应该更新):返回布尔值决定是否重新渲染,用于性能优化
getSnapshotBeforeUpdate(在更新前获取快照):在 DOM 更新前调用,返回值将作为 componentDidUpdate 的第三个参数
componentDidUpdate:更新后立即调用,适合执行 DOM 操作或网络请求
componentWillUnmount(组件将要卸载):组件卸载前调用,用于清理定时器、取消网络请求、移除订阅等

注意

  • 子组件内部状态变化 只影响子组件生命周期,不会影响父组件
  • 父组件进入更新并 render 时,通常会继续 reconcile 子组件;若子组件 shouldComponentUpdate 返回 false(或函数子被 memo 跳过),则子组件可不再 render,后续子生命周期也会相应跳过

说说 react 的生命周期

回答

shell
React 生命周期就是组件从创建、挂载、更新、卸载整个过程中,自动执行的一系列钩子函数,用来控制组件在不同阶段的行为。

# class 组件生命周期
挂载阶段:constructor 初始化 state、绑定方法 -> render:渲染虚拟 DOM -> componentDidMount:组件挂载到真实 DOM 后执行,适合:发起网络请求、DOM 操作、订阅、定时器 
更新阶段:props 变化、state 变化、forceUpdate 触发 -> render:重新渲染 -> componentDidUpdate:更新完成后执行,要加判断,避免无限循环
卸载阶段:componentWillUnmount 清除定时器、取消订阅、移除事件监听,防止内存泄漏

# 类组件生命周期可分为三个阶段:挂载阶段(constructor→render→componentDidMount)、更新阶段(shouldComponentUpdate→render→componentDidUpdate)、卸载阶段(componentWillUnmount)。开发中,我会把异步请求、DOM 操作放在 componentDidMount,避免阻塞渲染;在 componentDidUpdate 中处理更新后的逻辑(比如根据 props 变化重新请求数据);在 componentWillUnmount 中清理副作用(比如取消定时器、取消请求),防止内存泄漏。

# 函数组件: 通过 useEffect 依靠依赖项数组,统一模拟了挂载、更新、卸载三个阶段。
useEffect 是函数组件处理副作用的核心,它可以模拟类组件的多个生命周期:
模拟 componentDidMount:useEffect (() => { /* 逻辑 */ }, [])(空依赖);
模拟 componentDidUpdate:useEffect (() => { /* 逻辑 */ }, [依赖项])(指定依赖,依赖变化时执行);
模拟 componentWillUnmount:useEffect (() => { return () => { /* 清理逻辑 */ } }, [])(返回清理函数)。
实际使用中,我会注意依赖项的完整性,比如依赖 state/props 时必须显式声明,避免出现闭包陷阱导致的状态不一致问题。

为什么网络请求要放在 componentDidMount 而不是 constructor /render?

shell
 constructor:此时组件还没挂载,请求返回快的话,setState 可能触发多次渲染,且无法获取 DOM;
 render:render 会重复执行(比如 state/props 变化),会导致重复请求,触发死循环;
 componentDidMount:组件已挂载到真实 DOM,既能保证请求只执行一次(挂载阶段仅触发一次),也能安全操作 DOM/setState。

为什么 React 要废弃 componentWillMount /componentWillUpdate/componentWillReceiveProps?

shell
核心原因是 React 16 引入 Fiber 架构(异步渲染),这三个 "Will" 钩子可能被多次调用(比如渲染被中断后重新执行),导致:
不可预测的副作用(比如多次发请求、多次修改 state);
违背 "纯函数" 原则,增加 bug 风险。
替代方案:
componentWillReceiveProps static getDerivedStateFromProps(纯函数,无副作用);
componentWillUpdate getSnapshotBeforeUpdate(更新前获取 DOM 快照,无副作用);
componentWillMount 逻辑移到 constructor(初始化)+ componentDidMount(副作用)。

说一下 useEffect 的闭包陷阱及解决办法?

shell
# 陷阱表现:
函数组件每次渲染都会创建新的函数作用域,useEffect 捕获的是当前渲染的 state/props,导致内部访问的变量不是最新的(比如定时器里拿不到最新 state)。

# 解决办法
把依赖项加入数组:比如 useEffect(() => { timer = setInterval(() => console.log(count), 1000) }, [count])
 useRef 保存最新值:通过 ref 穿透闭包,const countRef = useRef(count); countRef.current = count;,定时器里取 countRef.current;
 useReducer:把状态更新逻辑抽离,通过 dispatch 获取最新状态。

componentDidUpdate 里 setState 为什么会导致无限循环?怎么避免?

shell
原因:componentDidUpdate 在组件更新后执行,若直接 setState,会触发组件重新更新,再次执行 componentDidUpdate,形成死循环;
避免方法:必须加条件判断,仅当 props/state 变化符合预期时才 setState:
componentDidUpdate(prevProps, prevState) {
  // 只有 props.id 变化时才更新
  if (this.props.id !== prevProps.id) {
    this.setState({ data: this.props.id });
  }
}

卸载阶段为什么必须清除定时器 / 订阅?不清除会有什么问题?

shell
问题:组件卸载后,定时器 / 订阅不会自动销毁,会持续占用内存,还可能尝试更新已卸载的组件状态,触发 React 警告;
做法:class 组件在 componentWillUnmount 里清除,函数组件在 useEffect 的返回函数里清除:
# class 组件
componentWillUnmount() {
  clearInterval(this.timer);
  this.subscription.unsubscribe();
}

# 函数组件
useEffect(() => {
  const timer = setInterval(() => {}, 1000);
  return () => clearInterval(timer); // 卸载时执行
}, []);