大話react生命週期2019:react-v16.3新生命週期總結

1、前言

React 從 v16 開始,像是跨入了新的時代,性能和新的 API 都使人矚目。從新認識 React,從從新認識生命週期開始。

爲了更好的支持異步渲染(Async Rendering),解決一些生命週期濫用可能致使的問題,React 從 V16.3
開始,對生命週期進行漸進式調整,同時在官方文檔也提供了使用的最佳實踐。前端

這裏咱們將簡要對比 React 新舊生命週期,從新認識一下 React 生命週期。java

React V16.3 新增的生命週期方法

getDerivedStateFromProps()
getSnapshotBeforeUpdate()react

逐漸廢棄的生命週期方法:

componentWillMount()
componentWillReceiveProps()
componentWillUpdate()ajax

雖然廢棄了這三個生命週期方法,可是爲了向下兼容,將會作漸進式調整。(詳情見#12028)
V16.3 並未刪除這三個生命週期,同時還爲它們新增以 UNSAFE_ 前綴爲別名的三個函數 UNSAFE_componentWillMount()UNSAFE_componentWillReceiveProps()UNSAFE_componentWillUpdate()數組

16.4 版本給出警告將會棄用 componentWillMount()componentWillReceiveProps()componentWillUpdate() 三個函數服務器

而後在 17 版本將會刪除 componentWillMount()componentWillReceiveProps()componentWillUpdate() 這三個函數,會保留使用 UNSAFE_componentWillMount()UNSAFE_componentWillReceiveProps()UNSAFE_componentWillUpdate()網絡

2、react-v16.3以前版本的生命週期(老生命週期)

圖示數據結構

clipboard.png

1.生命週期總覽

react的生命週期大概分爲框架

  • 組件裝載(Mount)組件第一次渲染到Dom樹
  • 組件更新(update)組件state,props變化引起的從新渲染
  • 組件卸載(Unmount)組件從Dom樹刪除

2.組件裝載過程

  • constructor: 在此初始化state,綁定成員函數this環境,props本地化
  • componentWillMount: 預裝載函數,不能進行修改state的操做,即便作了,也不會進行新數據狀態的渲染。在該函數中作的操做,均可以提早到構造函數中。
  • render: 渲染函數,惟一的必定不能省略的函數,必須有返回值,返回null或false表示不渲染任何DOM元素。它是一個僅僅用於渲染的純函數,返回值徹底取決於this.state和this.props,不能在函數中任何修改props、state、拉取數據等具備反作用的操做。render函數返回的是JSX的對象,該函數並不由於這渲染到DOM樹,什麼時候進行真正的渲染是有React庫決定的。(setState是一個異步函數)
  • componentDidMount: 掛載成功函數。該函數不會再render函數調用完成以後當即調用,由於render函數僅僅是返回了JSX的對象,並無當即掛載到DOM樹上,而componentDidMount是在組件被渲染到DOM樹以後被調用的。另外,componentDidMount函數在進行服務器端渲染時不會被調用。

3.組件更新過程

當組件掛載到DOM樹上以後,props/state被修改會致使組件進行更新操做。更新過程會以此調用以下的生命週期函數:dom

  • componentWillReceiveProps(nextProps): 該函數在組件進行更新以及父組件render函數(無論數據是否發生了改變)被調用後執行,this.props取得當前的props,nextProps傳入的是要更新的props。一般是比較this.props和nextProps來從新setState。
  • shouldComponentUpdate(nextProps, nextState): 返回bool值,true表示要更新,false表示不更新,使用得當將大大提升React組件的性能,避免不須要的渲染。
  • componentWillUpdate: 預更新函數。
  • render: 渲染函數。
  • componentDidUpdate: 更新完成函數。 相比裝載過程的生命週期函數,更新過程的生命週期函數使用的相對來講要少一些。經常使用的是componentWillReceiveProps、componentShouldUpdate,前者常常用於根據先後兩個數據去設置組件的狀態,然後者則是經常使用於優化,避免沒必要要的渲染。

4.組件卸載過程

卸載過程只涉及一個函數componentWillUnmount,當React組件要從DOM樹上刪除前,會調用一次這個函數。這個函數常常用於去除componentDidMount函數帶來的反作用,例如清除計時器刪除componentDidMount中創造的非React元素

5.注意事項

setState
要修改state,只能使用this.setState(),不能使用this.state.value='myData' 相似方式設置state,一是不會驅動從新渲染,二是極可能被後面的操做替換,形成沒法預知的錯誤。此外,React利用狀態隊列來實現setState的異步更新,避免頻繁地重複更新state。當同時作了不少setState操做的時候,react會智能的合併成一個setState,當須要肯定的setState完成後的操做,可使用

setState({}, () => {
// 在這裏進行state改變後的操做
})

setState的調用是有風險的,在某些生命週期函數中調用可能會無用甚至早恆循環調用致使崩潰。state的初始化通常在構造函數中實現;setState能夠在裝載過程的componentWillMount、componentDidMount中調用setState能夠在更新過程當中的componentWillReceiveProps、componentDidUpdate中調用

render
render是一個異步函數,render執行後並不會直接生成Dom,而是生成虛擬Dom節點(模擬HTML Dom節點的一個javaScript數據結構),什麼時候生成真實的DOM樹取決於react框架自己的計算

3、react-v16.3以後版本的生命週期(新生命週期)

圖示

clipboard.png

1.新的生命週期

getDerivedStateFromProps

  • 觸發時間(v16.4修正):組件每次被render的時候,包括在組件構建以後(虛擬dom以後,實際dom掛載以前),每次獲取新的props或state以後。在v16.3版本時,組件state的更新不會觸發該生命週期。
  • 每次接收新的props以後都會返回一個對象做爲新的state,返回null則說明不須要更新state.
  • 配合componentDidUpdate,能夠覆蓋componentWillReceiveProps的全部用法
  • getDerivedStateFromProps是一個靜態函數,因此函數體內不能訪問this,輸出徹底由輸入決定。
static getDerivedStateFromProps(nextProps, prevState) {
  //根據nextProps和prevState計算出預期的狀態改變,返回結果會被送給setState
}

getSnapshotBeforeUpdate

  • 觸發時間: update發生的時候,在render以後,在組件dom渲染以前。
  • 返回一個值,做爲componentDidUpdate的第三個參數。
  • 配合componentDidUpdate, 能夠覆蓋componentWillUpdate的全部用法。

2.刪除的生命週期

  • componentWillReceiveProps
  • componentWillMount
  • componentWillUpdate

差別
全部被刪除的生命週期函數,目前還湊合着用,可是隻要用了,開發模式下會有紅色警告,在下一個大版本(也就是React v17)更新時會完全廢棄。

3.生命週期功能替換一覽

static getDerivedStateFromProps(nextProps, prevState) {
    4. Updating state based on props
    7. Fetching external data when props change
  }

  constructor() {
    1. Initializing state
  }

  componentDidMount() {
    2. Fetching external data
    3. Adding event listeners (or subscriptions)
  }
  
  shouldComponentUpdate() {
  }
  
  render() {
  }
  
  getSnapshotBeforeUpdate(prevProps, prevState) {
    8. Reading DOM properties before an update
  }
  
  componentDidUpdate(prevProps, prevState, snapshot) {
    5. Invoking external callbacks
    6. Side effects on props change
  }
  
  componentWillUnmount() {
  }

// before

componentWillMount() {
      // 1. Initializing state
      // 2. Fetching external data
      // 3. Adding event listeners (or subscriptions)
  }
  componentWillReceiveProps() {
      // 4. Updating state based on props
      // 6. Side effects on props change
      // 7. Fetching external data when props change
  }
  componentWillUpdate(nextProps, nextState) {
      // 5. Invoking external callbacks
      // 8. Reading DOM properties before an update
  }

4、react生命週期總結

目前 react 16.8 +的生命週期分爲三個階段,分別是 掛載階段更新階段卸載階段

1.簡介

掛載階段:
constructor(props): 實例化。
static getDeriverdStateFromPropsprops 中獲取 state
render 渲染。
componentDidMount: 完成掛載。

更新階段:
static getDeriverdStateFromPropsprops 中獲取 state
shouldComponentUpdate 判斷是否須要重繪。
render 渲染。
getSnapshotBeforeUpdate 獲取快照。
componentDidUpdate 渲染完成後回調。

卸載階段:
componentWillUnmount 即將卸載。

錯誤處理:
static getDerivedStateFromError 從錯誤中獲取 state。
componentDidCatch 捕獲錯誤並進行處理。

2.建立階段 Mounting

組件實例建立並插入 DOM 時,按順序調用如下方法:

constructor()
static getDerivedStateFromProps()
componentWillMount()/UNSAFE_componentWillMount()(being deprecated)
render()
componentDidMount()
有定義 getDerivedStateFromProps 時,會忽略 componentWillMount()/UNSAFE_componentWillMount()
(詳情查看源碼)

1)constructor()
constructor(props)
構造函數一般用於:

  • 使用 this.state 來初始化 state
  • 給事件處理函數綁定 this

注意:ES6 子類的構造函數必須執行一次 super()。React 若是構造函數中要使用 this.props,必須先執行 super(props)。

2)static getDerivedStateFromProps()
static getDerivedStateFromProps(nextProps, prevState)
當建立時、接收新的 props 時、setState 時、forceUpdate 時會執行這個方法。
注意:v16.3 setState 時、forceUpdate 時不會執行這個方法,v16.4 修復了這個問題。
這是一個靜態方法,參數 nextProps 是新接收的 propsprevState 是當前的 state。返回值(對象)將用於更新 state,若是不須要更新則須要返回 null。

下面是官方文檔給出的例子

class ExampleComponent extends React.Component {
  // Initialize state in constructor,
  // Or with a property initializer.
  state = {
    isScrollingDown: false,
    lastRow: null,
  };

  static getDerivedStateFromProps(props, state) {
    if (props.currentRow !== state.lastRow) {
      return {
        isScrollingDown: props.currentRow > state.lastRow,
        lastRow: props.currentRow,
      };
    }

    // Return null to indicate no change to state.
    return null;
  }
}

這個方法的經常使用做用也很明顯了:父組件傳入新的 props 時,用來和當前的 state 對比,判斷是否須要更新 state。之前通常使用 componentWillReceiveProps 作這個操做。
這個方法在建議儘可能少用,只在必要的場景中使用,通常使用場景以下:

無條件的根據 props 更新 state
當 props 和 state 的不匹配狀況更新 state
詳情能夠參考官方文檔的最佳實踐 You Probably Don’t Need Derived State

3)componentWillMount()/UNSAFE_componentWillMount()(棄用)
UNSAFE_componentWillMount()
這個方法已經不推薦使用。由於在將來異步渲染機制下,該方法可能會屢次調用。它所行使的功能也能夠由 componentDidMount() 和 constructor() 代替:

以前有些人會把異步請求放在這個生命週期,其實大部分狀況下都推薦把異步數據請求放在 componentDidMount() 中
在服務端渲染時,一般使用 componentWillMount() 獲取必要的同步數據,可是可使用 constructor() 代替它。
可使用 setState,不會觸發 re-render

4)render
render()
每一個類組件中,render() 惟一必須的方法。

render() 正如其名,做爲渲染用,能夠返回下面幾種類型:

  • React 元素(React elements)
  • 數組(Arrays)
  • 片斷(fragments)
  • 插槽(Portals)
  • 字符串或數字(String and numbers)
  • 布爾值或 null(Booleans or null)

注意:
Arrays 和 String 是 v16.0.0 新增。
fragments 是 v16.2.0 新增。
Portals 是 V16.0.0 新增。

裏面不該該包含反作用,應該做爲純函數。

不能使用 setState。

5)componentDidMount()
componentDidMount()
組件完成裝載(已經插入 DOM 樹)時,觸發該方法。這個階段已經獲取到真實的 DOM。

通常用於下面的場景:

  • 異步請求 ajax
  • 添加事件綁定(注意在 componentWillUnmount 中取消,以避免形成內存泄漏)
  • 可使用 setState,觸發re-render,影響性能。

3.更新階段 Updating

componentWillReceiveProps()/UNSAFE_componentWillReceiveProps()(being deprecated)
static getDerivedStateFromProps()
shouldComponentUpdate()
componentWillUpdate()/UNSAFE_componentWillUpdate()(being deprecated)
render()
getSnapshotBeforeUpdate()
componentDidUpdate()

getDerivedStateFromProps 或者 getSnapshotBeforeUpdate 時,componentWillReceiveProps()/UNSAFE_componentWillReceiveProps()componentWillUpdate()/UNSAFE_componentWillUpdate() 不會執行 (詳情查看源碼)

1)componentWillReceiveProps()/UNSAFE_componentWillReceiveProps()(棄用)
UNSAFE_componentWillReceiveProps(nextProps)
這個方法在接收新的 props 時觸發,即便 props 沒有變化也會觸發。

通常用這個方法來判斷 props 的先後變化來更新 state,以下面的例子:

class ExampleComponent extends React.Component {
  state = {
    isScrollingDown: false,
  };

  componentWillReceiveProps(nextProps) {
    if (this.props.currentRow !== nextProps.currentRow) {
      this.setState({
        isScrollingDown:
          nextProps.currentRow > this.props.currentRow,
      });
    }
  }
}

這個方法將被棄用,推薦使用 getDerivedStateFromProps 代替。

可使用 setState

2)static getDerivedStateFromProps()
同 Mounting 時所述一致。

3)shouldComponentUpdate()
在接收新的 props 或新的 state 時,在渲染前會觸發該方法。

該方法經過返回 true 或者 false 來肯定是否須要觸發新的渲染。返回 false, 則不會觸發後續的 UNSAFE_componentWillUpdate()、render() 和 componentDidUpdate()(可是 state 變化仍是可能引發子組件從新渲染)。

因此一般經過這個方法對 props 和 state 作比較,從而避免一些沒必要要的渲染。

PureComponent 的原理就是對 props 和 state 進行淺對比(shallow comparison),來判斷是否觸發渲染。

4)componentWillUpdate()/UNSAFE_componentWillUpdate() (棄用)
UNSAFE_componentWillUpdate(nextProps, nextState)
當接收到新的 props 或 state 時,在渲染前執行該方法。

在之後異步渲染時,可能會出現某些組件暫緩更新,致使 componentWillUpdate 和 componentDidUpdate 之間的時間變長,這個過程當中可能發生一些變化,好比用戶行爲致使 DOM 發生了新的變化,這時在 componentWillUpdate 獲取的信息可能就不可靠了。

不能使用 setState

5)render()
同 Mounting 時所述一致。

6)getSnapshotBeforeUpdate()
getSnapShotBeforeUpdate(prevProps, prevState)
這個方法在 render() 以後,componentDidUpdate() 以前調用。

兩個參數 prevProps 表示更新前的 props,prevState 表示更新前的 state。

返回值稱爲一個快照(snapshot),若是不須要 snapshot,則必須顯示的返回 null —— 由於返回值將做爲 componentDidUpdate() 的第三個參數使用。因此這個函數必需要配合 componentDidUpdate() 一塊兒使用。

這個函數的做用是在真實 DOM 更新(componentDidUpdate)前,獲取一些須要的信息(相似快照功能),而後做爲參數傳給 componentDidUpdate。例如:在 getSnapShotBeforeUpdate 中獲取滾動位置,而後做爲參數傳給 componentDidUpdate,就能夠直接在渲染真實的 DOM 時就滾動到須要的位置。

下面是官方文檔給出的例子:

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

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Are we adding new items to the list?
    // Capture the scroll position so we can adjust scroll later.
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // If we have a snapshot value, we've just added new items.
    // Adjust scroll so these new items don't push the old ones out of view.
    // (snapshot here is the value returned from getSnapshotBeforeUpdate)
    if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }

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

7)componentDidUpdate()
componentDidUpdate(prevProps, prevState, snapshot)
這個方法是在更新完成以後調用,第三個參數 snapshot 就是 getSnapshotBeforeUpdate 的返回值。

正如前面所說,有 getSnapshotBeforeUpdate 時,必需要有 componentDidUpdate。因此這個方法的一個應用場景就是上面看到的例子,配合 getSnapshotBeforeUpdate 使用。

可使用 setState,會觸發 re-render,因此要注意判斷,避免致使死循環。

4.卸載階段 Unmounting

1)componentWillUnmount()

componentWillUnmount()
在組件卸載或者銷燬前調用。這個方法主要用來作一些清理工做,例如:

  • 取消定時器
  • 取消事件綁定
  • 取消網絡請求

不能使用 setState

5.錯誤處理 Error Handling

componentDidCatch()
componentDidCatch(err, info)
任何子組件在渲染期間,生命週期方法中或者構造函數 constructor 發生錯誤時調用。

錯誤邊界不會捕獲下面的錯誤:

  • 事件處理 (Event handlers) (由於事件處理不發生在 React 渲染時,報錯不影響渲染)
  • 異步代碼 (Asynchronous code) (e.g. setTimeout or requestAnimationFrame callbacks)
  • 服務端渲染 (Server side rendering)
  • 錯誤邊界自己(而不是子組件)拋出的錯誤

總結
React 生命週期能夠查看 生命週期圖
雖然 React 有作向下兼容,可是推薦儘可能避免使用廢棄的生命週期,而是擁抱將來,用新的生命週期替換它們。

若是你不想升級 React,可是想用新的生命週期方法,也是能夠的。使用 react-lifecycles-compat polyfill,能夠爲低版本的 React(0.14.9+)提供新的生命週期方法。

5、不能使用setState()的生命週期

clipboard.png

若是你以爲這篇文章對你有所幫助,那就順便點個贊吧,點贊收藏不迷路~

黑芝麻哇,白芝麻發,黑芝麻白芝麻哇發哈!

前端哇發哈

相關文章
相關標籤/搜索