React生命周期环

Avatar of Kingsley Silas
Kingsley Silas

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200美元免费额度!

React组件在其应用生命周期中会经历不同的阶段,尽管幕后可能没有明显的迹象表明发生了任何事情。

这些阶段是

  • 挂载
  • 更新
  • 卸载
  • 错误处理

在每个阶段中都有一些方法,可以在该阶段对组件执行特定操作。例如,当从网络获取数据时,您需要在componentDidMount()方法中调用处理API调用的函数,该方法在挂载阶段可用。

了解不同的生命周期方法对于React应用程序的开发非常重要,因为它允许我们在需要时精确地触发操作,而不会与其他操作混淆。我们将在本文中查看每个生命周期,包括可用的方法以及我们使用它们的场景类型。

挂载阶段

可以将挂载视为组件生命周期的初始阶段。在挂载发生之前,组件尚不存在——它仅仅是DOM眼中的一丝闪烁,直到挂载发生并将组件作为文档的一部分挂钩。

一旦组件挂载,我们就可以利用许多方法:constructor()render()componentDidMount()static getDerivedStateFromProps()。每个方法都有其自身的用途,因此让我们按顺序查看它们。

constructor()

当直接在组件上设置状态以将方法绑定在一起时,需要使用constructor()方法。以下是它的外观

// Once the input component is mounting...
constructor(props) {
  // ...set some props on it...
  super(props);
  // ...which, in this case is a blank username...
  this.state = {
    username: ''
  };
  // ...and then bind with a method that handles a change to the input
  this.handleInputChange = this.handleInputChange.bind(this);
}

需要注意的是,constructor是创建组件时调用的第一个方法。组件尚未渲染(即将到来),但DOM已经知道它,并且我们可以在它渲染之前挂钩到它。因此,这不是我们调用setState()或引入任何副作用的地方,因为,好吧,组件仍然处于构造阶段!

我之前写过一篇关于ref的使用的教程,我注意到的一件事是在使用React.createRef()时可以在constructor中设置ref。这是合理的,因为ref用于在没有props或不必使用更新值重新渲染组件的情况下更改值。

constructor(props) {
  super(props);
  this.state = {
    username: ''
  };
  this.inputText = React.createRef();
}

render()

render()方法是组件的标记在前端显示的地方。用户此时可以查看并访问它。如果您曾经创建过React组件,那么您已经熟悉它了——即使您没有意识到——因为它需要输出标记。

class App extends React.Component {
  // When mounting is in progress, please render the following!
  render() {
    return (
      <div>
        <p>Hello World!</p>
      </div>
    )
  }
}

但这并不是render()的全部用途!它还可以用于渲染组件数组

class App extends React.Component {
  render () {
    return [
      <h2>JavaScript Tools</h2>,
      <Frontend />,
      <Backend />
    ]
  }
}

……甚至组件片段

class App extends React.Component {
  render() {
    return (
      <React.Fragment>
        <p>Hello World!</p>
      </React.Fragment>
    )
  }
}

我们还可以使用它来渲染DOM层次结构之外的组件(类似于React Portal

// We're creating a portal that allows the component to travel around the DOM
class Portal extends React.Component {
  // First, we're creating a div element
  constructor() {
    super();
    this.el = document.createElement("div");
  }
  
  // Once it mounts, let's append the component's children
  componentDidMount = () => {
    portalRoot.appendChild(this.el);
  };
  
  // If the component is removed from the DOM, then we'll remove the children, too
  componentWillUnmount = () => {
    portalRoot.removeChild(this.el);
  };
  
  // Ah, now we can render the component and its children where we want
  render() {
    const { children } = this.props;
    return ReactDOM.createPortal(children, this.el);
  }
}

当然,render()可以——咳咳——渲染数字和字符串……

class App extends React.Component {
  render () {
    return "Hello World!"
  }
}

……以及null或布尔值

class App extends React.Component {
  render () {
    return null
  }
}

componentDidMount()

componentDidMount()名称是否泄露了它的含义?此方法在组件挂载后(即挂钩到DOM后)调用。在我之前关于在React中获取数据的教程中,您需要在这里向API发出请求以获取数据。

我们可以使用您的fetch方法

fetchUsers() {
  fetch(`https://jsonplaceholder.typicode.com/users`)
    .then(response => response.json())
    .then(data =>
      this.setState({
        users: data,
        isLoading: false,
      })
    )
  .catch(error => this.setState({ error, isLoading: false }));
}

然后在componentDidMount()钩子中调用该方法

componentDidMount() {
  this.fetchUsers();
}

我们还可以添加事件监听器

componentDidMount() {
  el.addEventListener()
}

很不错,对吧?

static getDerivedStateFromProps()

它的名字有点长,但static getDerivedStateFromProps()并不像听起来那么复杂。它在挂载阶段的render()方法之前以及更新阶段之前调用。它返回一个对象来更新组件的状态,或者在没有要更新的内容时返回null

为了理解它的工作原理,让我们实现一个计数器组件,该组件的counter状态将具有某个值。仅当maxCount的值较高时,此状态才会更新。maxCount将从父组件传递。

这是父组件

class App extends React.Component {
  constructor(props) {
    super(props)
    
    this.textInput = React.createRef();
    this.state = {
      value: 0
    }
  }
  
  handleIncrement = e => {
    e.preventDefault();
    this.setState({ value: this.state.value + 1 })
  };

  handleDecrement = e => {
    e.preventDefault();
    this.setState({ value: this.state.value - 1 })
  };

  render() {
    return (
      <React.Fragment>
        <section className="section">
          <p>Max count: { this.state.value }</p>
          <button
            onClick={this.handleIncrement}
            class="button is-grey">+</button>
          <button
            onClick={this.handleDecrement}
            class="button is-dark">-</button>          
        </section>
        <section className="section">
          <Counter maxCount={this.state.value} />
        </section>
      </React.Fragment>
    )
  }
}

我们有一个按钮用于增加maxCount的值,我们将其传递给Counter组件。

class Counter extends React.Component {
  state={
    counter: 5
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (prevState.counter < nextProps.maxCount) {
      return {
        counter: nextProps.maxCount
      };
    } 
    return null;
  }

  render() {
    return (
      <div className="box">
        <p>Count: {this.state.counter}</p>
      </div>
    )
  }
}

Counter组件中,我们检查counter是否小于maxCount。如果是,我们将counter设置为maxCount的值。否则,我们什么也不做。

您可以使用下面的Pen进行操作,以查看它在前端是如何工作的

查看Pen
getDerivedStateFromProps
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.


更新阶段

当组件的propsstate发生变化时,就会发生更新阶段。与挂载类似,更新也有自己的一套可用方法,我们接下来将介绍它们。也就是说,值得注意的是,render()getDerivedStateFromProps()也会在此阶段触发。

shouldComponentUpdate()

当组件的stateprops发生变化时,我们可以使用shouldComponentUpdate()方法来控制组件是否应该更新。此方法在渲染发生之前以及接收状态和props时调用。默认行为为true。要每次状态或props更改时都重新渲染,我们可以执行以下操作

shouldComponentUpdate(nextProps, nextState) {
  return this.state.value !== nextState.value;
}

当返回false时,组件不会更新,而是调用render()方法来显示组件。

getSnapshotBeforeUpdate()

我们可以做的一件事是在某个时间点捕获组件的状态,这就是getSnapshotBeforeUpdate()的设计目的。它在render()之后但在任何新更改提交到DOM之前调用。返回值作为第三个参数传递给componentDidUpdate()

它将先前状态和props作为参数

getSnapshotBeforeUpdate(prevProps, prevState) {
  // ...
}

至少根据我的经验,此方法的使用案例很少。它是那些您可能不会经常使用到的生命周期方法之一。

componentDidUpdate()

componentDidUpdate()添加到方法列表中,其中名称大致说明了一切。如果组件更新,那么我们可以在此时使用此方法挂钩到它,并将其传递给组件的先前propsstate

componentDidUpdate(prevProps, prevState) {
  if (prevState.counter !== this.state.counter) {
    // ...
  }
}

如果您曾经使用过getSnapshotBeforeUpdate(),您还可以将返回值作为参数传递给componentDidUpdate()

componentDidUpdate(prevProps, prevState, snapshot) {
  if (prevState.counter !== this.state.counter) {
    // ....
  }
}

卸载阶段

这里我们基本上是在看安装阶段的反向操作。正如您可能预料的那样,当组件从 DOM 中移除且不再可用时,就会发生卸载 (unmounting)

这里我们只有一个方法:componentWillUnmount()

此方法在组件卸载并销毁之前被调用。在这里,我们希望在组件离开后执行任何必要的清理工作,例如移除可能在componentDidMount()中添加的事件监听器,或清除订阅。

// Remove event listener 
componentWillUnmount() {
  el.removeEventListener()
}

错误处理阶段

组件中可能会出现错误,这会导致错误。我们已经有一段时间使用错误边界来帮助解决这个问题。这个错误边界组件利用了一些方法来帮助我们处理可能遇到的错误。

getDerivedStateFromError()

我们使用getDerivedStateFromError()来捕获从子组件抛出的任何错误,然后我们使用它来更新组件的状态。

class ErrorBoundary extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      hasError: false
    };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <h1>Oops, something went wrong :(</h1>
      );
    }

    return this.props.children;
  }
}

在这个例子中,当子组件抛出错误时,ErrorBoundary组件将显示“Oops, something went wrong”。我们在这篇文章中提供了更多关于此方法的信息:React 16.6.0 中发布的功能总结

componentDidCatch()

虽然getDerivedStateFromError()适用于在发生副作用(如错误记录)的情况下更新组件的状态,但我们应该使用componentDidCatch(),因为它在提交阶段(DOM 已更新)被调用。

componentDidCatch(error, info) {
  // Log error to service
}

getDerivedStateFromError()componentDidCatch()都可以在ErrorBoundary组件中使用。

class ErrorBoundary extends React.Component {
  
constructor(props) {
  super(props);
  this.state = {
    hasError: false
  };
}

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // Log error to service
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <h1>Oops, something went wrong :(</h1>
      );
    }

    return this.props.children;
  }
}

这就是 React 组件的生命周期!

了解 React 组件如何与 DOM 交互是一件很酷的事情。很容易认为一些“魔法”发生了,然后页面上就会出现一些东西。但是 React 组件的生命周期表明,这种混乱是有秩序的,并且它旨在赋予我们极大的控制力,使我们能够从组件进入 DOM 到离开 DOM 的整个过程中都能控制事情的发生。

我们在相对较短的空间内涵盖了很多内容,但希望这能让你很好地了解 React 如何处理组件,以及在处理过程的各个阶段我们拥有哪些能力。如果你对我们在这里讨论的任何内容不清楚,请随时提出任何问题,我将尽我所能提供帮助!