跳转至

React组件的三个时间点:挂载、更新、卸载

挂载(Mount)

组件的创建(挂载) 指的是组件第一次被渲染到 DOM 中的过程,是组件生命周期的起点。

1. 触发时机

  • 应用首次加载时,根组件(如 <App />)被首次渲染。
  • 父组件渲染时,子组件从 “不存在” 变为 “存在”(例如:条件渲染中 {show && <Child />} 中 show 从 false 变为 true)。
  • 列表渲染中,新的子组件被添加(如数组新增元素,且 key 是新值)。

2. 发生过程

组件挂载时,React 会完成以下工作:

  • 初始化组件实例: - 函数组件:创建独立的闭包环境(存储 useState、useRef 等 Hooks 的状态)。 - 类组件:创建类的实例(new Component()),执行 constructor 初始化 this.state。
  • 渲染虚拟 DOM:执行组件函数(函数组件)或 render 方法(类组件),生成虚拟 DOM 树。
  • 插入真实 DOM:将虚拟 DOM 转换为真实 DOM,并插入到页面中。
  • 执行挂载后逻辑: - 函数组件:useEffect 中不依赖任何值(空依赖数组 []) 的回调函数会执行(仅一次)。 - 类组件:执行 componentDidMount 生命周期方法。

3. 典型场景

  • 首次加载页面时,页面上的所有组件第一次出现。
  • 点击 “显示弹窗” 按钮,弹窗组件从隐藏变为显示(首次渲染弹窗)。

总之一切都重新开始

卸载(Unmount)

组件的卸载指的是组件从 DOM 中被移除,且组件实例被销毁的过程。

1. 触发时机

  • 条件渲染中组件 “消失”:例如 {show && <Child />} 中 show 从 true 变为 false,子组件被卸载。
  • 路由切换:当路由从 /pageA 切换到 /pageB 时,PageA 组件会被卸载。
  • 列表渲染中组件被移除:例如列表数组删除某个元素,且对应的 key 消失,该元素对应的组件会被卸载。
  • 父组件卸载:父组件被卸载时,其所有子组件会被递归卸载。

2. 发生过程

卸载时,React 会执行以下工作:

  • 移除真实 DOM:将组件对应的 DOM 节点从页面中删除。
  • 清理组件实例: - 函数组件:销毁闭包环境(useState、useRef 等状态不再保留)。 - 类组件:销毁类实例(this 指向的实例被回收)。
  • 执行清理逻辑: - 函数组件:useEffect 中返回的清理函数会执行(例如移除事件监听、取消定时器、中断网络请求等)。 - 类组件:执行 componentWillUnmount 生命周期方法。

3. 重要性

卸载时的清理逻辑至关重要,若未正确清理,可能导致内存泄漏:

  • 例如:组件挂载时添加了 window.scroll 事件监听,卸载时未移除,事件监听会一直存在(引用着已卸载的组件闭包 / 实例),导致内存无法释放。
  • 再如:未取消的定时器(setInterval)在组件卸载后仍会执行,浪费资源。

卸载时候所有的useEffect中的清理函数都会执行,不管是否有依赖数组,也不管依赖着谁,统统执行

更新(Update)

组件的重新渲染(更新) 指的是组件已经挂载到 DOM 后,再次执行渲染逻辑并更新 DOM的过程(组件实例未销毁,只是内容更新)。

1. 触发时机

组件重新渲染的核心原因是 “需要展示的内容发生了变化”,具体触发条件包括:

  • 自身状态(state)变化:
    调用 setState(类组件)或 useState 的更新函数(如 setCount(c => c + 1)),当新状态与旧状态浅比较不相等时,触发重新渲染。
    例:计数器组件点击后 count 变化,触发重新渲染。
  • 接收的 props 变化:
    父组件重新渲染时,传给子组件的 props 与上次的 props浅比较不相等,子组件会重新渲染。
    例:父组件的 name 变化,传给子组件的 name 也变化,子组件重新渲染。
  • 父组件重新渲染:
    即使子组件的 props 没有变化,只要父组件重新渲染,子组件默认也会跟着重新渲染(React 的默认行为,避免漏更,但可能导致不必要的渲染)。
  • context 变化:
    组件使用 useContext 或 Context.Consumer 订阅了某个 context,当 context 的值变化时,组件会重新渲染。
  • 强制重新渲染:
    调用 forceUpdate(类组件),强制组件跳过状态比较直接重新渲染(不推荐,破坏 React 的更新逻辑)。

2. 发生过程

重新渲染时,React 会执行以下步骤:

  • 重新执行渲染逻辑:

    - 函数组件:重新执行组件函数(生成新的虚拟 DOM)。 - 类组件:重新执行 render 方法(生成新的虚拟 DOM)。
    注意:函数组件重新执行时,会创建新的局部变量,但 useState、useRef 等 Hooks 会保留之前的状态(通过 React 内部的链表维护)。 - 虚拟 DOM Diff(对比):React 会对比新旧虚拟 DOM 树,找出差异部分(“最小更新范围”)。 - 更新真实 DOM:只将差异部分对应的真实 DOM 进行更新(而非重新创建整个组件的 DOM),这是 React 性能优化的核心(“DOM Diffing”)。

3. 相关注意点

  • 重新渲染≠DOM 全量更新:React 通过 Diffing 算法只更新变化的 DOM,避免不必要的 DOM 操作。
  • 不必要的重新渲染:如果子组件在 props 不变时仍频繁重新渲染,会浪费性能。可通过 React.memo(缓存组件)、useMemo(缓存值)、useCallback(缓存函数)优化。
  • 闭包陷阱:函数组件重新渲染时,会创建新的闭包。如果 useEffect 等钩子的依赖处理不当,可能捕获旧的状态 / 属性(需正确设置依赖数组)。

在更新状态中的useEffect行为

如果依赖数组发生了变化:

  1. 执行上一次的清理函数(若有)
  2. 执行本次的副作用。

如果依赖数组没有变化,则稳坐不动。

状态更新与 useEffect 的常见问题

1. 依赖数组缺失导致的闭包陷阱 若 useEffect 内部使用了某个状态,但未将其加入依赖数组,状态更新后,useEffect 不会重新执行,导致副作用中始终引用旧状态。

2. 过度依赖导致的频繁执行 若依赖数组包含频繁变化的状态(如每次渲染创建的新对象),会导致 useEffect 频繁执行清理和副作用,浪费性能。