Published on

React 生命周期

Authors
  • avatar
    Name
    Reeswell
    Twitter

Class 中的生命周期

这里主要讲述react16.4之后的生命周期,可以分成三个阶段:

Mounting 阶段

这个阶段涉及到组件的初始化和第一次渲染。

constructor():组件实例化时调用

  • 用于初始化 state 获取路由信息
  • 绑定事件处理函数、比如绑定 this , 节流,防抖等。

getDerivedStateFromProps(nextProps,prevState):在 render() 方法之前调用。(componentWillMount被这个代替了)

  • 用于根据 props 计算 state。

  • 可以props 进行格式化,过滤等。

  • 返回一个对象去更新state,可作为shouldComponentUpdate 第二个参数 newState。

render():渲染组件,必须实现。

  • createElement创建元素 , cloneElement 克隆元素 ,React.children 遍历 children 的操作。

componentDidMount():在组件第一次渲染后调用,用于执行一些初始化操作。

  • AJAX 请求数据。
  • DOM 操作,DOM 事件监听器。

Updating 阶段

这个阶段涉及到组件被重新渲染的情况,如 props 或 state 的改变。

getDerivedStateFromProps():同上。(componentWillReceiveProps被这个代替了,componentWillUpdate也可以理解被这个代代替是在shouldComponentUpdate之后执行的) shouldComponentUpdate(newProps,newState,nextContext):在 render() 方法之前调用,用于确定组件是否需要重新渲染。

  • 返回true则组件重新渲染,返回false则组件不渲染

render():同上。

getSnapshotBeforeUpdate(prevProps,preState) :在 render() 方法之后、更新 DOM 之前调。(代替了componentWillUpdate)

  • 用于获取当前 DOM 的快照,比如 DOM 元素位置等。
  • 将返回一个值作为一个snapShot,传递给 componentDidUpdate作为第三个参数。

componentDidUpdate(prevProps, prevState, snapshot):在组件更新后调用,可以获取 DOM 最新状态。

Unmounting 阶段

这个阶段涉及到组件被卸载的情况。

componentWillUnmount():在组件被卸载前调用。

  • 用于清除计时器、取消网络请求、清除事件监听器。

Hook生命周期

useInsertionEffect

DOM 更新之前执行。

  • 解决 CSS-in-JS 在渲染中注入样式的性能问题,例如在useLayoutEffect 动态生成 style 标签,可能会导致浏览器再次重回和重排。

useLayoutEffect

在 DOM 更新之后,浏览器绘制之前同步执行(requestAnimationFrame)。

function doSomethingBeforePaint() {
  // 执行需要在绘制之前完成的操作
}
// requestAnimationFrame可以让浏览器在下一次绘制之前执行指定的回调函数
requestAnimationFrame(doSomethingBeforePaint);

  • 件渲染完成后同步执行,会阻塞浏览器的渲染进程。
  • 适合操作DOM,改变布局。通常用于执行一些需要更新页面的操作,例如更新动画或调整元素位置和大小等。

useEffect

用于在组件渲染后执行一些操作,可以模拟 componentDidMount、componentDidUpdate 和 componentWillUnmount 生命周期。

第一个参数 callback: callback 和 setTimeout回调函数一样,放入任务队列,等到主线程任务完成,DOM 更新,js 执行完成,视图绘制完毕,才执行。

  • 组件渲染完成后异步执行,不会阻塞浏览器的渲染进程。
  • 可以进行AJAX 请求数据
  • 获取DOM,DOM 事件监听器。
  • 不推荐DOM 操作,浏览器再次回流和重绘,视图上可能会造成闪现突兀的效果。

第一个参数 callback返回方法destory:下一次callback执行前执行或者组件卸载前执行。

  • 用于清除计时器、取消网络请求、清除事件监听器。

第二个参数作为依赖项:是一个数组,可以有多个依赖项。

  • 依赖项改变,执行上一次callback返回值destory方法,然后执行callback。
  • 当依赖为[]时,初始化执行一次。当没有依赖时,每一次执行函数组件,都会执行该 effect。

Demo详解

import React, { Component } from 'react';

// 创建阶段
// 更新阶段
// 卸载阶段

// 创建阶段
// 创建阶段主要分成了以下几个生命周期方法:
// constructor
// getDerivedStateFromProps
// render
// componentDidMount

// 更新阶段
// 该阶段的函数主要为如下方法:
// getDerivedStateFromProps
// shouldComponentUpdate
// render
// getSnapshotBeforeUpdate
// componentDidUpdate

// 卸载阶段
// componentWillUnmount
interface ExampleProps {
  name: string;
}

interface ExampleState {
  count: number;
}

class Example extends Component<ExampleProps, ExampleState> {
  constructor(props: ExampleProps) {
    super(props);
    console.log(
      `实例过程中自动调用的方法,在方法内部通过super关键字获取来自父组件的props
constructor是ES6中的一个构造函数,在组件实例化的时候被调用。它用于初始化组件的state和绑定事件处理函数等操作。
注意:在constructor中调用setState是不安全的,因为在这个阶段React还没有生成虚拟DOM,调用setState不会触发重新渲染,但是会直接修改state`,
      'constructor',
    );
    this.state = {
      count: 0,
    };
  }
  static getDerivedStateFromProps(props: ExampleProps, state: ExampleState) {
    console.log(
      `该方法是新增的生命周期方法,是一个静态的方法,因此不能访问到组件的实例
执行时机:组件创建和更新阶段,不论是props变化还是state变化,也会调用
在每次render方法前调用,第一个参数为即将更新的props,第二个参数为上一个状态的state,可以比较props 和 state来加一些限制条件,防止无用的state更新
该方法需要返回一个新的对象作为新的state或者返回null表示state状态不需要更新,在该方法中,我们可以对props和state进行比较,加入一些限制条件,避免无用的state更新`,
      'getDerivedStateFromProps',
    );
    return null;
  }
  shouldComponentUpdate(nextProps: ExampleProps, nextState: ExampleState) {
    console.log(
      `用于告知组件本身基于当前的props和state是否需要重新渲染组件,默认情况返回true
执行时机:到新的props或者state时都会调用,通过返回true或者false告知组件更新与否
一般情况,不建议在该周期方法中进行深层比较,会影响效率
同时也不能调用setState,否则会导致无限循环调用更新`,
      'shouldComponentUpdate',
    );
    return nextState.count % 2 === 0;
  }
  componentDidMount() {
    console.log(
      `组件挂载到真实DOM节点后执行,其在render方法之后执行
此方法多用于执行一些数据获取,初始化一些状态,事件监听等操作
`,
      'componentDidMount',
    );
  }
  getSnapshotBeforeUpdate(prevProps: ExampleProps, prevState: ExampleState) {
    console.log(
      `该周期函数在render后执行,执行之时DOM元素还没有被更新
该方法返回的一个Snapshot值,作为componentDidUpdate第三个参数传入
此方法的目的在于获取组件更新前的一些信息,比如组件的滚动位置之类的,在组件更新后可以根据这些信息恢复一些UI视觉上的状态
`,
      'getSnapshotBeforeUpdate',
    );
    return 'from getSnapshotBeforeUpdate';
  }
  componentDidUpdate(
    prevProps: ExampleProps,
    prevState: ExampleState,
    snapshot: any,
  ) {
    console.log(
      `执行时机:组件更新结束后触发 在该方法中,可以根据前后的props和state的变化做相应的操作,如获取数据,修改DOM样式等`,
      'componentDidUpdate',
    );
  }

  componentWillUnmount() {
    console.log(
      `此方法用于组件卸载前,清理一些注册是监听事件,或者取消订阅的网络请求等
一旦一个组件实例被卸载,其不会被再次挂载,而只可能是被重新创建
`,
      'componentWillUnmount',
    );
  }

  handleClick = () => {
    this.setState((prevState) => ({ count: prevState.count + 1 }));
  };

  render() {
    console.log(
      `生成虚拟DOM对象,返回该组件的虚拟DOM树,类组件必须实现的方法,用于渲染DOM结构,可以访问组件state与prop属性
    注意: 不要在 render 里面 setState, 否则会触发死循环导致内存崩溃`,
      'render',
    );
    const { name } = this.props;
    const { count } = this.state;
    return (
      <div>
        <h1>Hello, {name}!</h1>
        <p>You clicked the button {count} times.</p>
        <button onClick={this.handleClick}>Click me</button>
      </div>
    );
  }
}

export default Example;

在使用 React Hooks 的函数组件中,没有生命周期方法,但是可以使用一些与生命周期相关的 Hooks,如 useEffect、useLayoutEffect、useMemo 等。下面是一个示例组件,演示了 React Hooks 中的生命周期相关 Hooks 和它们的顺序:

import React, { useState, useEffect, useLayoutEffect, useMemo } from "react";

function HooksLifecycleDemo() {
  const [count, setCount] = useState(0);
  console.log("render");

  useEffect(() => {
    console.log("componentDidMount");
    return () => console.log("componentWillUnmount");
  }, []);

  useEffect(() => {
    console.log("componentDidUpdate");
    return () => console.log("cleanup");
  }, [count]);

  useLayoutEffect(() => {
    console.log("useLayoutEffect");
  }, [count]);

  const memoizedValue = useMemo(() => {
    console.log("useMemo");
    return count * 2;
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <p>Memoized value: {memoizedValue}</p>
    </div>
  );
}

export default HooksLifecycleDemo;

上述组件包含了以下 Hooks:

useState: 用于定义组件的状态。 useEffect: 用于在组件渲染后执行一些操作,可以模拟 componentDidMount、componentDidUpdate 和 componentWillUnmount 生命周期。 useLayoutEffect: 与 useEffect 类似,但会在浏览器绘制之前同步执行。 useMemo: 用于缓存计算结果,避免重复计算,类似于 shouldComponentUpdate 生命周期。 我们可以通过在控制台打印各个 Hooks 的名称,来查看它们的顺序和调用次数。注意,由于 Hooks 可能会被多次调用,因此它们可能会在不同的时间调用多次,而不像生命周期方法那样只调用一次。