學習React生命週期

React生命週期總體分爲三個階段:創建階段(Mounting)、運行階段(Updating)和卸載階段(Unmounting)。也有種說法分爲四個階段:初始化階段(Initialization)、創建階段(Mounting)、運行階段(Updating)和卸載階段(Unmounting)

一、React v16.0前的生命週期
在這裏插入圖片描述
1.初始化階段(Initialization)
也就是下方代碼中的類的構造方法,Test類繼承Component類,也同時繼承這個React的基類(也就繼承了render方法和生命週期方法)。
在這裏插入圖片描述
Super是調用基類的構造方法。獲取props。
Constructor 構造方法 可初始化state
2.創建階段(Mounting)
特點:該階段的函數只執行一次。
(1)ComponentWillMount()
組件被掛在到DOM之前調用,在render()之前被調用,因此在這個方法setState將不會觸發頁面重新渲染。可以將這裏的操作放在constructor()中。(發送ajax請求獲取數據);
(2)render()
根據組件中的props和state進行渲染頁面(兩者的任何變化都會導致頁面的重新渲染),返回一個ReactComponent類型的對象,不負責組件的實際渲染工作,之後由react根據此元素去渲染頁面DOM。
(注意:不要在render中調用this.setState()否則會造成遞歸渲染)
React.render的實現是在ReactMount中,源碼:

render: function(nextElement, container, callback) {

  var prevComponent = instancesByReactRootID[getReactRootID(container)];

  if (prevComponent) {
    var prevElement = prevComponent._currentElement;
    if (shouldUpdateReactComponent(prevElement, nextElement)) {
      return ReactMount._updateRootComponent(
        prevComponent,
        nextElement,
        container,
        callback
      ).getPublicInstance();
    } else {
      ReactMount.unmountComponentAtNode(container);
    }
  }

  var reactRootElement = getReactRootElementInContainer(container);
  var containerHasReactMarkup =
    reactRootElement && ReactMount.isRenderedByReact(reactRootElement);

  var shouldReuseMarkup = containerHasReactMarkup && !prevComponent;

  var component = ReactMount._renderNewRootComponent(
    nextElement,
    container,
    shouldReuseMarkup
  ).getPublicInstance();
  if (callback) {
    callback.call(component);
  }
  return component;
}

如果prevComponent爲空也就是第一次進行掛載該ReactElement,直接添加即可,如果已經掛載過會調用shouldUpdateReactComponent(prevElement,nextElement)進行判斷對組件進行update還是delete操作。
shouldUpdateReactComponent:如果prevElement類型是string或者number,並且nextElement也爲string或者number時候返回true;如果prevElement和nextElement都是對象且key和type屬相相同時,則返回prevElement._owner == nextElement._owner。
如果需要更新,則調用ReactMount…_updateRootComponent函數進行Reconciliation,並返回該組件;否則刪除該組件,具體操作則是刪除container的所有子元素。然後判斷shouldReuseMarkup,對於初次掛載的ReactElement而言,該標記爲false。最後通過調用_renderNewRootComponent方法將ReactElement渲染到DOM上,並獲取對應的ReactComponent對象,最後執行回調並返回組件對象。
對於_renderNewRootComponent方法,通過調用instantiateReactComponent(nextElement, null)來實例化組件,並在ReactMount的緩存中註冊組件,批量執行更新ReactUpdates.batchedUpdates,最終通過_mountImageIntoNode方法將虛擬節點插入到DOM中。
(3)ComponentDidMount()
組件被掛載到DOM後且只執行一次;可以獲取組件內容的DOM對象並進行DOM操作;可以發送請求獲取數據;可以通過this.setState()修改狀態的值,修改後會進行頁面的重新渲染。
3.運行階段(Updating)
該階段的函數執行多次(每當組件的props或者state改變的時候都會觸發運行階段的函數);
(1)ComponentWillReceiveProps(nextProps)
可以通過this.props獲取到上一次的值,通過比較this.props和nextProps來判斷是否屬性發生變化,如果需要響應屬性的變化可以在該方法中使用this.setState()處理狀態。*在該函數調用this.setState將不會引起二次渲染。
(2)shouldComponentUpdate(nextProps,nextState)
根據這個方法的返回值決定是否重新渲染組件,返回值需布爾值,通過這個方法降低組件渲染的頻率提升組件的性能。
(3)ComponentWillUpdate(nextProps,nextState)
組件將要跟新前執行,不能調用setState()否則會造成遞歸渲染
(4)render()同Mounting階段的render
(5)ComponentDidUpdate(prevProps,prevState)
組件被跟新後調用,可以操作DOM
4.卸載階段(Unmounting)
(1)ComponentWillUnmount()
在卸載組件的時候執行清理工作,eg:清理定時器,清除componentDidMount創建的DOM對象。
二、React V16.4生命週期

變更緣由
原來(React v16.0前)的生命週期在React v16推出的Fiber之後就不合適了,因爲如果要開啓async rendering,在render函數之前的所有函數,都有可能被執行多次。
原來(React v16.0前)的生命週期有哪些是在render前執行的呢?
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
如果開發者開了async rendering,而且又在以上這些render前執行的生命週期方法做AJAX請求的話,那AJAX將被無謂地多次調用。。。明顯不是我們期望的結果。而且在componentWillMount裏發起AJAX,不管多快得到結果也趕不上首次render,而且componentWillMount在服務器端渲染也會被調用到(當然,也許這是預期的結果),這樣的IO操作放在componentDidMount裏更合適。
禁止不能用比勸導開發者不要這樣用的效果更好,所以除了shouldComponentUpdate,其他在render函數之前的所有函數(componentWillMount,componentWillReceiveProps,componentWillUpdate)都被getDerivedStateFromProps替代。
也就是用一個靜態函數getDerivedStateFromProps來取代被deprecate的幾個生命週期函數,就是強制開發者在render之前只做無副作用的操作,而且能做的操作侷限在根據props和state決定新的state
React v16.0剛推出的時候,是增加了一個componentDidCatch生命週期函數,這只是一個增量式修改,完全不影響原有生命週期函數;但是,到了React v16.3,大改動來了,引入了兩個新的生命週期函數。
新引入了兩個新的生命週期函數:getDerivedStateFromProps,getSnapshotBeforeUpdate
getDerivedStateFromProps
getDerivedStateFromProps本來(React v16.3中)是隻在創建和更新(由父組件引發部分),也就是不是不由父組件引發,那麼getDerivedStateFromProps也不會被調用,如自身setState引發或者forceUpdate引發。
React v16.3 的生命週期圖
在這裏插入圖片描述
React v16.3
這樣的話理解起來有點亂,在React v16.4中改正了這一點,讓getDerivedStateFromProps無論是Mounting還是Updating,也無論是因爲什麼引起的Updating,全部都會被調用,具體可看React v16.4 的生命週期圖。
React v16.4後的getDerivedStateFromProps
static getDerivedStateFromProps(props, state) 在組件創建時和更新時的render方法之前調用,它應該返回一個對象來更新狀態,或者返回null來不更新任何內容。
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate() 被調用於render之後,可以讀取但無法使用DOM的時候。它使您的組件可以在可能更改之前從DOM捕獲一些信息(例如滾動位置)。此生命週期返回的任何值都將作爲參數傳遞給componentDidUpdate()。
官網給的例子:

class ScrollingList extends React.Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    //我們是否要添加新的 items 到列表?
    // 捕捉滾動位置,以便我們可以稍後調整滾動.
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    //如果我們有snapshot值, 我們已經添加了 新的items.
    // 調整滾動以至於這些新的items 不會將舊items推出視圖。
    // (這邊的snapshot是 getSnapshotBeforeUpdate方法的返回值)
    if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      <div ref={this.listRef}>{/* ...contents... */}</div>
    );
  }
}

參考:
程墨Morgan–React v16.3之後的組件生命週期函數
徐超–《React進階之路》
Aermin–詳解react生命週期(包括react16版)
ReactJS分析之入口函數render(網址:https://www.cnblogs.com/accordion/p/4501118.html)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章