React生命周期

React的生命周期从广义上分为挂载、渲染、卸载三个阶段,在React的整个生命周期中提供很多钩子函数在生命周期的不同时刻调用。

描述

此处描述的是使用class类组件提供的生命周期函数,每个组件都包含自己的生命周期方法,通过重写这些方法,可以在运行过程中特定的阶段执行这些方法,常用的生命周期有constructor()render()componentDidMount()componentDidUpdate()componentWillUnmount()

挂载过程

当组件实例被创建并插入DOM中时,其生命周期调用顺序如下:

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

在这个阶段的componentWillMount()生命周期即将过时,在新代码中应该避免使用。

更新过程

当组件的propsstate发生变化时会触发更新,组件更新的生命周期调用顺序如下:

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

在这个阶段的componentWillUpdate()componentWillReceiveProps()生命周期即将过时,在新代码中应该避免使用。

卸载过程

当组件从DOM中移除时,组件更新的生命周期调用顺序如下:

  • componentWillUnmount()

错误处理

当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:

  • static getDerivedStateFromError()
  • componentDidCatch()

生命周期

constructor()

React组件挂载之前,会调用它的构造函数,如果不初始化state或不进行方法绑定,则不需要为React组件实现构造函数。在为React.Component子类实现构造函数时,应在其他语句之前前调用super(props),否则this.props在构造函数中可能会出现未定义的错误。 通常在React中构造函数仅用于以下两种情况:

  • 通过给this.state赋值对象来初始化内部state
  • 为事件处理函数绑定实例。
constructor(props) {
    super(props);
}

static getDerivedStateFromProps()

getDerivedStateFromProps静态方法会在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用,它应返回一个对象来更新state,如果返回null则不更新任何内容。此方法无权访问组件实例,如果确实需要,可以通过提取组件props的纯函数及class之外的状态,在getDerivedStateFromProps()和其他class方法之间重用代码。此外,不管原因是什么,都会在每次渲染前触发此方法。

static getDerivedStateFromProps(props, state) {}

render()

render()方法是class组件中唯一必须实现的方法,render()函数应该为纯函数,这意味着在不修改组件state的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。如需与浏览器进行交互,请在componentDidMount()或其他生命周期方法中执行操作,保持render()为纯函数。当render被调用时,它会检查this.propsthis.state的变化并返回以下类型之一:

  • React元素,通常通过JSX创建,例如<div />会被React渲染为DOM节点,<MyComponent />会被React渲染为自定义组件,无论是<div />还是<MyComponent />均为React元素。

  • 数组或fragments,使得render方法可以返回多个元素。

  • Portals,可以渲染子节点到不同的DOM子树中。

  • 字符串或数值类型,它们在DOM中会被渲染为文本节点。

  • 布尔类型或null,什么都不渲染,主要用于支持返回test && <Child />的模式,其中test为布尔类型。

render() {}

componentDidMount()

componentDidMount()会在组件挂载后(即插入DOM树后)立即调用,依赖于DOM节点的初始化应该放在这里,如需通过网络请求获取数据,此处是实例化请求的好地方。这个方法是比较适合添加订阅的地方,如果添加了订阅,请不要忘记在componentWillUnmount()里取消订阅。
你可以在componentDidMount()里直接调用setState(),它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前,如此保证了即使在render()两次调用的情况下,用户也不会看到中间状态,请谨慎使用该模式,因为它会导致性能问题。通常应该在constructor()中初始化state,如果你的渲染依赖于DOM节点的大小或位置,比如实现modalstooltips等情况下,你可以使用此方式处理。

componentDidMount() {}

shouldComponentUpdate()

propsstate发生变化时,shouldComponentUpdate()会在渲染执行之前被调用,返回值默认为true,首次渲染或使用forceUpdate()时不会调用该方法。根据shouldComponentUpdate()的返回值,判断React组件的输出是否受当前stateprops更改的影响。默认行为是state每次发生变化组件都会重新渲染,大部分情况下,你应该遵循默认行为。
此方法仅作为性能优化的方式而存在,不要企图依靠此方法来阻止渲染,因为这可能会产生bug,你应该考虑使用内置的PureComponent组件,而不是手动编写shouldComponentUpdate()PureComponent会对propsstate进行浅层比较,并减少了跳过必要更新的可能性。
如果你一定要手动编写此函数,可以将this.propsnextProps以及this.statenextState进行比较,并返回false以告知React可以跳过更新。请注意,返回false并不会阻止子组件在state更改时重新渲染。不建议在shouldComponentUpdate()中进行深层比较或使用JSON.stringify(),这样非常影响效率,且会损害性能。目前如果shouldComponentUpdate()返回false,则不会调用UNSAFE_componentWillUpdate()render()componentDidUpdate()。后续版本React可能会将shouldComponentUpdate视为提示而不是严格的指令,并且当返回false时仍可能导致组件重新渲染。

shouldComponentUpdate(nextProps, nextState) {}

getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate()在最近一次渲染输出(提交到DOM节点)之前调用,它使得组件能在发生更改之前从DOM中捕获一些信息(例如滚动位置),此生命周期的任何返回值将作为参数传递给componentDidUpdate(),该方法应返回snapshot的值或null
此用法并不常见,但它可能出现在UI处理中,如需要以特殊方式处理滚动位置的聊天线程等。

getSnapshotBeforeUpdate(prevProps, prevState) {}

componentDidUpdate()

componentDidUpdate()会在更新后会被立即调用,首次渲染不会执行此方法。当组件更新后,可以在此处对DOM进行操作,如果你对更新前后的props进行了比较,也可以选择在此处进行网络请求(例如,当props未发生变化时,则不会执行网络请求。如果shouldComponentUpdate()返回值为false,则不会调用componentDidUpdate()
你也可以在componentDidUpdate()中直接调用setState(),但请注意它必须被包裹在一个条件语句里,否则会导致死循环,因为他将无限次触发componentDidUpdate()。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。
如果组件实现了getSnapshotBeforeUpdate()生命周期(不常用),则它的返回值将作为componentDidUpdate()的第三个参数snapshot参数传递,否则此参数将为undefined

componentDidUpdate(prevProps, prevState, snapshot) {}

componentWillUnmount()

componentWillUnmount()会在组件卸载及销毁之前直接调用,在此方法中执行必要的清理操作,例如清除timer、取消网络请求或清除在componentDidMount()中创建的订阅等。
componentWillUnmount()中不应调用setState(),因为该组件将永远不会重新渲染,组件实例卸载后,将永远不会再挂载它。

componentWillUnmount() {}

static getDerivedStateFromError()

此生命周期会在后代组件抛出错误后被调用,它将抛出的错误作为参数,并返回一个值以更新stategetDerivedStateFromError()会在渲染阶段调用,因此不允许出现副作用,如遇此类情况,请改用componentDidCatch()

static getDerivedStateFromError(error) {}

componentDidCatch()

此生命周期在后代组件抛出错误后被调用,componentDidCatch()会在提交阶段被调用,因此允许执行副作用,它应该用于记录错误之类的情况它接收两个参数:

  • error: 抛出的错误。
  • info: 带有componentStack key的对象,其中包含有关组件引发错误的栈信息。
componentDidCatch(error, info) {}

示例

React组件的常用生命周期示例。

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <title>React生命周期</title>
</head>

<body>
  <div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">

  class Clock extends React.Component {
    constructor(props) {
      super(props);
      this.state = { date: new Date() };
    }
    componentDidMount() {
      console.log("ComponentDidMount", this);
      console.log(this.props);
      console.log(this.state);
      console.log("");
      this.timer = setInterval(() => this.tick(), 1000);
    }
    componentWillUnmount() {
      console.log("ComponentWillUnmount", this);
      console.log(this.props);
      console.log(this.state);
      console.log("");
      clearInterval(this.timer);
    }
    tick() {
      this.setState({ date: new Date() });
    }
    render() {
      return (
        <div>
          <h1>{this.props.tips}</h1>
          <h2>Now: {this.state.date.toLocaleTimeString()}</h2>
        </div>
      );
    }
  }

  class App extends React.Component{
    constructor(props){
      super(props);
      this.state = { 
        showClock: true,
        tips: "Hello World!"
      }
    }
    componentDidUpdate(prevProps, prevState) {
      console.log("ComponentDidUpdate", this);
      console.log(this.props);
      console.log(this.state);
      console.log("");
    }
    updateTips() {
      this.setState((state, props) => ({
        tips: "React update"
      }));
    }
    changeDisplayClock() {
      this.setState((state, props) => ({
        showClock: !this.state.showClock
      }));
    }
    render() {
      return (
        <div>
          {this.state.showClock && <Clock tips={this.state.tips} />}
          <button onClick={() => this.updateTips()}>更新tips</button>
          <button onClick={() => this.changeDisplayClock()}>改变显隐</button>
        </div>
      );
    }
  }

  var vm = ReactDOM.render(
    <App />,
    document.getElementById("root")
  );
</script>

</html>

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://www.jianshu.com/p/b331d0e4b398 https://www.cnblogs.com/soyxiaobi/p/9559117.html https://zh-hans.reactjs.org/docs/react-component.html https://zh-hans.reactjs.org/docs/state-and-lifecycle.html https://www.runoob.com/react/react-component-life-cycle.html https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/