1. 程式人生 > >React生命週期分析

React生命週期分析

在 V16 版本中引入了 Fiber 機制。這個機制一定程度上的影響了部分生命週期的呼叫,並且也引入了新的 2 個 API 來解決問題。
在之前的版本中,如果你擁有一個很複雜的複合元件,然後改動了最上層元件的state,那麼呼叫棧可能會很長,呼叫棧過長,再加上中間進行了複雜的操作,就可能導致長時間阻塞主執行緒,帶來不好的使用者體驗。Fiber 就是為了解決該問題而生。Fiber 本質上是一個虛擬的堆疊幀,新的排程器會按照優先順序自由排程這些幀,從而將之前的同步渲染改成了非同步渲染,在不影響體驗的情況下去分段計算更新。對於非同步渲染,現在渲染有兩個階段:reconciliation 和 commit 。前者過程是可以打斷的,後者不能暫停,會一直更新介面直到完成。


Reconciliation 階段


• componentWillMount
• componentWillReceiveProps
• shouldComponentUpdate
• componentWillUpdate


Commit 階段


• componentDidMount
• componentDidUpdate
• componentWillUnmount
因 為 reconciliation 階 段 是 可 以 被 打 斷 的, 所 以 reconciliation 階 段 會執 行 的 生 命 周 期 函 數 就 可 能 會 出 現 調 用 多 次 的 情 況, 從 而 引 起 Bug。 所 以 對 於reconciliation 階段呼叫的幾個函式,除了 shouldComponentUpdate 以外,其他都應
該避免去使用,並且 V16 中也引入了新的 API 來解決這個問題。


getDerivedStateFromProps 用於替換 componentWillReceiveProps ,該函式會在初始化和 update 時被呼叫
 

getSnapshotBeforeUpdate 用於替換 componentWillUpdate ,該函式會在 update後 DOM 更新前被呼叫,用於讀取最新的 DOM 資料。
 

V16 生命週期函式用法建議
 

class ExampleComponent extends React.Component {
// 用於初始化 state
constructor() {}
// 用於替換 `componentWillReceiveProps` ,該函式會在初始化和 `update` 時被呼叫
// 因為該函式是靜態函式,所以取不到 `this`
// 如果需要對比 `prevProps` 需要單獨在 `state` 中維護
static getDerivedStateFromProps(nextProps, prevState) {}
// 判斷是否需要更新元件,多用於元件效能優化
shouldComponentUpdate(nextProps, nextState) {}
// 元件掛載後呼叫
// 可以在該函式中進行請求或者訂閱
componentDidMount() {}
// 用於獲得最新的 DOM 資料
getSnapshotBeforeUpdate() {}
// 元件即將銷燬
// 可以在此處移除訂閱,定時器等等
componentWillUnmount() {}
// 元件銷燬後呼叫
componentDidUnMount() {}
// 元件更新後呼叫
componentDidUpdate() {}
// 渲染元件函式
render() {}
// 以下函式不建議使用
UNSAFE_componentWillMount() {}
UNSAFE_componentWillUpdate(nextProps, nextState) {}
UNSAFE_componentWillReceiveProps(nextProps) {}
}

setState
 

setState 在 React 中是經常使用的一個 API,但是它存在一些問題,可能會導致犯錯,核心原因就是因為這個 API 是非同步的。
首先 setState 的呼叫並不會馬上引起 state 的改變,並且如果你一次呼叫了多個 setState ,那麼結果可能並不如你期待的一樣。
handle() {
// 初始化 `count` 為 0
console.log(this.state.count) // -> 0
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
console.log(this.state.count) // -> 0
}
第一,兩次的列印都為 0,因為 setState 是個非同步 API,只有同步程式碼執行完畢才會執行。setState 非同步的原因我認為在於,setState 可能會導致 DOM 的重繪,如果呼叫一次就馬上去進行重繪,那麼呼叫多次就會造成不必要的效能損失。設計成非同步
的話,就可以將多次呼叫放入一個佇列中,在恰當的時候統一進行更新過程。第二,雖然呼叫了三次 setState ,但是 count 的值還是為 1。因為多次呼叫會合併為一次,只有當更新結束後 state 才會改變,三次呼叫等同於如下程式碼
Object.assign(
{},
{ count: this.state.count + 1 },
{ count: this.state.count + 1 },
{ count: this.state.count + 1 },
)
當然你也可以通過以下方式來實現呼叫三次 setState 使得 count 為 3

handle() {
this.setState((prevState) => ({ count: prevState.count + 1 }))
this.setState((prevState) => ({ count: prevState.count + 1 }))
this.setState((prevState) => ({ count: prevState.count + 1 }))
}


如果你想在每次呼叫 setState 後獲得正確的 state ,可以通過如下程式碼實現

handle() {
this.setState((prevState) => ({ count: prevState.count + 1 }), () => {
console.log(this.state)
})
}