React生命週期

WindrunnerMax發表於2020-12-05

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/

相關文章