了解清楚React生命周期的前世今生

121 阅读9分钟

大家好,我是鱼樱!!!

关注公众号【鱼樱AI实验室】持续每天分享更多前端和AI辅助前端编码新知识~~喜欢的就一起学反正开源至上,无所谓被诋毁被喷被质疑文章没有价值~~~坚持自己观点

写点笔记写点生活~写点经验。

在当前环境下,纯前端开发者可以通过技术深化、横向扩展、切入新兴领域以及产品化思维找到突破口。

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

前置

生命周期一直是框架中的重要知识块之一,只有清楚了解每个阶段可以干什么的事情,才能精准控制代码的准确性

生命周期

生命周期:组件从诞生到销毁会经历一系列的过程,该过程就叫做生命周期。React在组件的生命周期中提供了一系列的钩子函数(类似于事件),可以让开发者在函数中注入代码,这些代码会在适当的时候运行。

生命周期仅存在于类组件中,函数组件每次调用都是重新运行函数,旧的组件即刻被销毁

旧版生命周期 (React < 16.0.0)

image.png

  1. constructor
    1. 同一个组件对象只会创建一次
    2. 不能在第一次挂载到页面之前,调用setState,为了避免问题,构造函数中严禁使用setState
  2. componentWillMount
    1. 正常情况下,和构造函数一样,它只会运行一次
    2. 可以使用setState,但是为了避免bug,不允许使用,因为在某些特殊情况下,该函数可能被调用多次
  3. render
    1. 返回一个虚拟DOM,会被挂载到虚拟DOM树中,最终渲染到页面的真实DOM中
    2. render可能不只运行一次,只要需要重新渲染,就会重新运行
    3. 严禁使用setState,因为可能会导致无限递归渲染
  4. componentDidMount
    1. 只会执行一次
    2. 可以使用setState
    3. 通常情况下,会将网络请求、启动计时器等一开始需要的操作,书写到该函数中
  5. 组件进入活跃状态
  6. componentWillReceiveProps
    1. 即将接收新的属性值
    2. 参数为新的属性对象
    3. 该函数可能会导致一些bug,所以不推荐使用
  7. shouldComponentUpdate
    1. 指示React是否要重新渲染该组件,通过返回true和false来指定
    2. 默认情况下,会直接返回true
  8. componentWillUpdate
    1. 组件即将被重新渲染
  9. componentDidUpdate
    1. 往往在该函数中使用dom操作,改变元素
  10. componentWillUnmount
    1. 通常在该函数中销毁一些组件依赖的资源,比如计时器

新版生命周期 (React >= 16.0.0)

image.png

React官方认为,某个数据的来源必须是单一的

  1. getDerivedStateFromProps
    1. 通过参数可以获取新的属性和状态
    2. 该函数是静态的
    3. 该函数的返回值会覆盖掉组件状态
    4. 该函数几乎是没有什么用
  2. getSnapshotBeforeUpdate
    1. 真实的DOM构建完成,但还未实际渲染到页面中。
    2. 在该函数中,通常用于实现一些附加的dom操作
    3. 该函数的返回值,会作为componentDidUpdate的第三个参数

以下是React 16之前与之后生命周期的完整对比,结合新旧版本的差异与演进:


一、React 16之前(旧版)生命周期

1. 挂载阶段(Mounting)
  1. constructor(props)
    初始化state和方法绑定。
  2. componentWillMount()
    组件挂载前调用(服务器端渲染会触发),但存在副作用风险。
  3. render()
    生成虚拟DOM结构,需保持纯净。
  4. componentDidMount()
    DOM挂载完成后调用,用于网络请求、DOM操作。
2. 更新阶段(Updating)
  1. componentWillReceiveProps(nextProps)
    父组件传递新props时触发(非首次)。
  2. shouldComponentUpdate(nextProps, nextState)
    控制是否重新渲染,默认返回true
  3. componentWillUpdate(nextProps, nextState)
    更新前调用,不可调用setState
  4. render()
    重新生成虚拟DOM。
  5. componentDidUpdate(prevProps, prevState)
    更新完成后执行副作用操作。
3. 卸载阶段(Unmounting)
  1. componentWillUnmount()
    清理定时器、取消订阅等资源释放。
特殊场景
  • 强制更新this.forceUpdate()跳过shouldComponentUpdate直接触发更新。

二、React 16及之后(新版)生命周期

1. 挂载阶段(Mounting)
  1. constructor(props)
    同旧版。
  2. static getDerivedStateFromProps(props, state)
    静态方法,替代componentWillReceiveProps,根据props派生state
  3. render()
    未变化。
  4. componentDidMount()
    同旧版。
2. 更新阶段(Updating)
  1. static getDerivedStateFromProps(props, state)
    同挂载阶段逻辑。
  2. shouldComponentUpdate(nextProps, nextState)
    同旧版。
  3. render()
    未变化。
  4. getSnapshotBeforeUpdate(prevProps, prevState)
    新增方法,DOM更新前捕获信息(如滚动位置)。
  5. componentDidUpdate(prevProps, prevState, snapshot)
    新增snapshot参数接收捕捉的信息。
3. 卸载阶段(Unmounting)
  1. componentWillUnmount()
    同旧版。
4. 错误处理阶段(新增)
  1. static getDerivedStateFromError(error)
    捕获子组件错误并更新state
  2. componentDidCatch(error, info)
    记录错误日志。
废弃的生命周期
  • componentWillMountcomponentWillReceivePropscomponentWillUpdate被标记为UNSAFE_前缀。

三、新旧版本对比总结

特性React 16前React 16+
挂载阶段componentWillMount废弃componentWillMount,引入getDerivedStateFromProps
更新阶段componentWillReceiveProps触发更新使用getDerivedStateFromProps替代
DOM更新捕获新增getSnapshotBeforeUpdate
错误边界新增getDerivedStateFromErrorcomponentDidCatch

四、代码示例(新版)

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  static getDerivedStateFromProps(props, state) {
    if (props.initialCount !== state.count) {
      return { count: props.initialCount };
    }
    return null;
  }

  componentDidMount() {
    // 初始化操作
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    return document.getElementById('list').scrollHeight;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot !== null) {
      // 使用快照值更新DOM
    }
  }

  componentWillUnmount() {
    // 清理资源
  }
}

以上生命周期演进体现了React对副作用控制和异步渲染的优化方向,开发者应优先使用新API并避免废弃方法。

React 函数组件通过 Hooks API 模拟类组件的生命周期行为

React 函数组件通过 Hooks API 模拟类组件的生命周期行为,主要依赖于 useEffectuseLayoutEffect 实现不同阶段的逻辑管理。以下是函数组件生命周期的对应实现方式:


一、生命周期对应关系

1. 挂载阶段(Mounting)
  • 组件初始化:
    使用 useState 初始化状态,类似类组件的 constructor

    const [state, setState] = useState(initialValue);
    
  • DOM 挂载完成:
    通过 useEffect 的空依赖数组模拟 componentDidMount

    useEffect(() => {
      // 网络请求、订阅或 DOM 操作
      return () => { /* 卸载阶段的清理逻辑 */ };
    }, []); // 空依赖数组确保仅执行一次
    

2. 更新阶段(Updating)
  • 响应状态/属性变化:
    useEffect 添加依赖项数组,模拟 componentDidUpdate

    useEffect(() => {
      // 当 state 或 props 变化时执行
    }, [state, props]); // 指定依赖项
    
  • DOM 更新前捕获信息:
    使用 useLayoutEffect 同步执行逻辑,类似 getSnapshotBeforeUpdate

    useLayoutEffect(() => {
      // 同步获取 DOM 信息(如滚动位置)
      return () => { /* 清理逻辑 */ };
    }, [dependencies]);
    

3. 卸载阶段(Unmounting)
  • 资源清理:
    useEffect 中返回清理函数,模拟 componentWillUnmount
    useEffect(() => {
      const timer = setInterval(() => {}, 1000);
      return () => clearInterval(timer); // 组件卸载时清理
    }, []);
    

二、特殊场景与注意事项

1. 首次渲染与更新分离
  • 使用 useRef 标记首次渲染,避免更新阶段逻辑在挂载时触发:
    const isMounted = useRef(false);
    useEffect(() => {
      if (!isMounted.current) {
        isMounted.current = true; // 初次渲染
      } else {
        // 后续更新逻辑
      }
    }, [dependencies]);
    
2. 同步副作用处理
  • useLayoutEffect vs useEffect:
    • useLayoutEffect 在 DOM 更新后同步执行,适合需要立即获取/修改 DOM 的场景。
    • useEffect 异步执行,避免阻塞渲染流程。

三、完整示例代码

import { useState, useEffect, useLayoutEffect } from 'react';

function FunctionComponent(props) {
  const [count, setCount] = useState(0);

  // 模拟 componentDidMount
  useEffect(() => {
    console.log('组件挂载完成');
    return () => console.log('组件卸载');
  }, []);

  // 模拟 componentDidUpdate(依赖 count)
  useEffect(() => {
    if (count > 0) console.log('count 更新:', count);
  }, [count]);

  // 同步获取 DOM 信息
  useLayoutEffect(() => {
    const element = document.getElementById('target');
    if (element) console.log('元素高度:', element.offsetHeight);
  });

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>点击 {count}</button>
      <div id="target">内容区域</div>
    </div>
  );
}

四、与类组件对比总结

类组件生命周期函数组件实现方式
constructoruseState 初始化状态
componentDidMountuseEffect 空依赖数组
componentDidUpdateuseEffect 带依赖项数组
componentWillUnmountuseEffect 返回清理函数
getSnapshotBeforeUpdateuseLayoutEffect 同步操作 DOM

五、最佳实践

  1. 避免滥用依赖项:精确指定依赖数组,防止无限循环。
  2. 优先异步副作用:默认使用 useEffect,仅在必要时使用 useLayoutEffect
  3. 逻辑复用:通过自定义 Hook 封装生命周期逻辑(如 useMountuseUnmount)。

函数组件的生命周期管理更简洁直观,但需注意 Hooks 的执行顺序和依赖管理规则。

React 生命周期是组件化开发的核心机制,其重要性体现在对组件行为的精细控制和开发效率的提升上。以下是关键原因:


生命周期的重要性

一、组件行为的可控性

  1. 精准调度不同阶段的逻辑
    生命周期函数允许开发者在组件的初始化、更新、销毁等阶段注入逻辑,例如:

    • 挂载阶段初始化数据(如 constructor 初始化 state);
    • 更新阶段优化渲染(如 shouldComponentUpdate 跳过无效渲染);
    • 销毁阶段释放资源(如 componentWillUnmount 清理定时器)。
  2. 响应外部变化
    通过 getDerivedStateFromPropscomponentDidUpdate,组件可动态适配 props 变化或用户交互。


二、性能优化与资源管理

  1. 避免无效渲染
    shouldComponentUpdate 可通过对比新旧 props/state 阻止不必要的渲染,极大提升性能。

  2. 资源生命周期管理
    确保异步操作(如网络请求、订阅事件)与组件生命周期同步,防止内存泄漏。


三、框架演进与最佳实践

  1. 适应异步渲染架构
    React 16+ 废弃 componentWillMount 等不安全方法,引入 getSnapshotBeforeUpdate,优先支持异步渲染模式下的稳定性和可预测性。

  2. 错误边界机制
    新增的 getDerivedStateFromErrorcomponentDidCatch 可捕获子组件错误,增强应用健壮性。


四、开发体验提升

  1. 调试与问题定位
    明确的生命周期阶段划分,帮助开发者快速定位渲染异常或副作用问题(如数据更新未触发预期行为时,需检查 componentDidUpdate 逻辑)。

  2. 代码可维护性
    规范化的生命周期流程使组件逻辑更结构化,促进团队协作与代码复用。


总结

React 生命周期通过定义明确的组件阶段边界,为开发者提供了行为控制粒度性能优化工具错误处理机制。理解并合理运用这些函数,是构建高性能、可维护 React 应用的基础。