求上進的人,不要總想著靠誰,人都是自私的,自己才是最靠得住的人。
React 中生命週期劃時代幾個節點,React 16.2 之前處於老的生命週期,之後提出了新的生命週期。而函式式元件在 React 16.8 之前是沒有狀態和生命週期的,在 React 16.8 版本透過引入 Hooks 使得函式式元件也能有狀態和生命週期了。
1. 初始化階段
1.1 componentWillMount:
元件即將掛載,初始化資料作用,即 render 之前最後一次修改狀態的機會。
// 元件即將掛載
componentWillMount() {
// 初始化資料作用
console.log("componentWillMount")
}
/* 在 16.2 之後版本使用會出現以下警告 ⚠️⚠️⚠️
react-dom.development.js:86 Warning: componentWillMount has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.
* Move code with side effects to componentDidMount, and set initial state in the constructor.
* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.
Please update the following components: App
*/
// 元件即將掛載 - 強制去掉警告,UNSAFE 提示開發者這是一個不安全的生命週期方法。
UNSAFE_componentWillMount() {
// 初始化資料作用
console.log("componentWillMount")
}
componentWillMount 在16.2 之後官方不推薦使用了,這是因為 16.2 的時候 React 發生了一個改變,推出了幾個新的生命週期,老的生命週期方法被替代掉了,不推薦使用。 那麼為什麼 React 要推出新的生命週期呢?
在 React 16.2 中透過對 diff 演算法的更新,更加最佳化它的效能。提出了一個 Fiber 技術(纖維、分片、切片,比執行緒更小的一種概念)。因為我們傳統的 React 它在建立、更新狀態之後會建立新的虛擬 Dom 樹,會對比老的虛擬 Dom 樹,這個過程是同步的,如果資料量比較小還好。如果這個資料量非常多的情況下即元件非常多的情況下(例如:幾百個元件),這個時候更新操作,會導致我們瀏覽器假死、卡頓,這個時候點什麼都沒有反應,因為它忙著新老虛擬 Dom 的對比,就是它在對比兩個超級大的物件,裡面包含了很多小物件,這時瀏覽器無法處理其它事件,所以導致卡頓影響體驗。
所以這個東西就是一個邊緣化的問題,你的元件達到這樣一個程度,它真的會出現假死的情況。所以 React Fiber 技術就是來最佳化了虛擬 Dom 的 diff 演算法,它把建立 Dom 和元件渲染整個過程拆分成了無數個小的分片任務來執行。可以認為這個任務無比的碎片化,這個時候如果有優先順序較高的任務就先執行優先順序較高的任務。當優先順序較低的任務在執行時候突然來了優先順序較高的任務,這個時候會打斷正在執行的低優先順序任務,先執行優先順序高的任務。所謂的低優先順序任務就是 componentWillMount 中去找哪些節點將要去掛載到頁面中,而高優先順序任務就是 render 正在渲染,didMount 掛載完成。這個時候我們低優先順序的任務(找出那些需要更新的 Dom)這個過程是會被打斷的,而我們更新 Dom 這個過程是不能被打斷的,只能一鼓作氣做完的,而 willMount 很不幸它是處在這個要找出來那些 Dom 是需要被更新的。所以這個過程是可以被打斷的,所以可以認為 willMount 在忙著找出那些狀態需要更新。因為接下來在 render 中就要開始更新了,didMount 就更新完成了。這個時候 willMount 找是處於低優先順序的,而這個時候 render 正在更新,因為碎片化任務,他可能還不是同步的。即某個元件可能處在在找那個狀態需要更新,那個 Dom 需要更新,而那邊元件已經到了 render 渲染部分了,這個時候就吧低優先順序的任務給砍掉了。砍掉怎麼辦,會儲存嗎?不會。只能下次再來一遍,再來找那個節點需要更新。所以這個生命週期就可能會觸發多次這樣一個問題(失去了唯一性),所以這是一個有隱患的生命週期方法,所以這裡不推薦使用。
1.2 render
元件正式掛載渲染,只能訪問 this.props 和 this.state,不允許修改狀態和 Dom 輸出。
1.3 componentDidMount
元件掛載完成,成功 render 並渲染完成真實 DOM 之後觸發,可以訪問、修改 Dom。
componentDidMount() {
// 資料請求axios
// 訂閱函式呼叫
// setInterval
// 基於建立的完的dom進行初始化時候,例如 BetterScroll 使用
console.log("componentDidMount")
}
2. 執行中階段
2.1 componentWillUpdate
元件即將更新,不能修改屬性和狀態,會造成死迴圈。非安全被棄用,同 componentWillMount。
// 元件即將更新
componentWillUpdate() {
console.log("componentWillUpdate")
}
/* 在 16.2 之後版本使用會出現以下警告 ⚠️⚠️⚠️
react-dom.development.js:86 Warning: componentWillUpdate has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.
* Move data fetching code or side effects to componentDidUpdate.
* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.
Please update the following components: App
*/
// 元件即將更新 - 強制去掉警告,UNSAFE 提示開發者這是一個不安全的生命週期方法。
UNSAFE_componentWillUpdate() {
console.log("componentWillUpdate")
}
2.2 render
元件正式掛載渲染,只能訪問 this.props 和 this.state,不允許修改狀態和 Dom 輸出。
2.3 componentDidUpdate
元件更新完成,成功 render 並渲染完成真實 DOM 之後觸發,可以訪問、修改 Dom。
// 元件更新完成 - 接收兩個行參,老的屬性、老的狀態
componentDidUpdate(prevProps, prevState) {
console.log(prevState)
console.log("componentDidUpdate")
}
2.4 shouldComponentUpdate
scu 控制元件是否應該更新,即是否執行 render 函式。
// 元件是否應該更新?- 接受兩個行參,新的屬性、新的狀態
shouldComponentUpdate(nextProps, nextState) {
if (JSON.stringify(this.state) !== JSON.stringify(nextState)) {
return true
} else {
return false
}
}
2.5 componentWillReceiveProps
父元件修改屬性觸發,非安全被棄用,同 componentWillMount。處在 diff 中第一個階段,找到哪些需要更新的 Dom。例如:如果父元件連續多次修改屬性傳遞將觸發多次 ajax 請求等。
// 父元件修改屬性觸發,應用在子元件中才有意義
componentWillReceiveProps(nextProps) {
// 最先獲得父元件傳來的屬性,可以利用屬性進行ajax或者邏輯處理
// 把屬性轉換為孩子的自己的狀態等
}
3. 銷燬階段
3.1 componentWillUnmount
在刪除元件之前進行清理操作,比如計時器和事件監聽器。
4. 新生命週期
4.1 getDerivedStateFromProps
getDerivedStateFromProps 第一次的初始化元件以及後續的更新過程中(包括自身狀態更新以及父傳子),返回一個物件作為新的 state,返回 null 則說明不需要在這裡更新 state。
在初始化中代替 componentWillMount 。在父傳子中能代替 componentWillReceiveProps。
這裡不能做非同步操作,因為這裡 return 是立即返回的。
static getDerivedStateFromProps(nextProps, nextState) {
console.log("getDerivedStateFromProps")
return {
}
}
4.2 getSnapshotBeforeUpdate
getSnapshotBeforeUpdate 取代了 componentDidUpdate,觸發時間為 update 發生的時候,在 render之後 Dom 渲染之前返回一個值,作為 componentDidUpdate 的第三個引數。
import React, { Component } from 'react'
export default class App extends Component {
state = {
}
// getDerivedStateFromProps 第一次的初始化元件以及後續的更新過程中(包括自身狀態更新以及父傳子),返回一個物件作為新的 state,返回 null 則說明不需要在這裡更新 state
// 這裡不能做非同步操作,因為這裡 return 是立即返回的
static getDerivedStateFromProps(nextProps, nextState) {
console.log("getDerivedStateFromProps")
return {
}
}
getSnapshotBeforeUpdate() {
return 100
}
componentDidUpdate(prevProps, prevState, value) {
console.log(value)
}
render() {
return (
<div>
<button onClick={()=>{
this.setState({
})
}}>修改</button>
</div>
)
}
}
5. React 中效能最佳化
5.1 shouldComponentUpdate
React 手動最佳化,控制元件自身或者子元件是否需要更新,尤其在子元件非常多的情況下,需要進行最佳化。
5.2 PureComponent
React 自動最佳化,PureComponent 會幫你比較新 props 跟舊的 props,新的 state 和老的 state(值相等,或者物件含有相同的屬性、且屬性值相等),決定 shouldComponentUpdate 返回 true 或者 false,從而決定要不要呼叫 render function。
注意:如果你的 state 或 props『永遠都會變』,那 PureComponent 並不會比較快,因為 shallowEqual 也需要花時間,比如倒數計時功能,這就不適合使用 Pure Component 了。
5.3 快取技術
React.Component 是使用 ES6 classes 方式定義 React 元件的基類:
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
PureComponent 和 memo 僅作為效能最佳化的方式而存在。但請不要依賴它來“阻止”渲染,因為這會產生 bug。PureComponnet 和 memo 都是透過對 props 值的淺比較來決定該元件是否需要更新的。
2.1 PureComponent (類元件)
React.PureComponent 與 React.Component 很相似。兩者的區別在於 React.Component 並未實現 shouldComponentUpdate(),而 React.PureComponent 中以淺層對比 props 和 state 的方式來實現了該函式。
如果賦予 React 元件相同的 props 和 state,render() 函式會渲染相同的內容,那麼在某些情況下使用 React.PureComponent 可提高效能。
2.2 memo(函式式元件)
函式元件快取 memo,為啥起 memo 這個名字?在計算機領城,記記化是一種主要用來提高計算機程式速度的最佳化技術方案。它將開銷較大的函式呼叫的返回結果儲存起來,當同樣的輸入再次發生時,則這回快取好的資料,以此提升運算效率。
React.memo 為高階元件。它與 React.PureComponent 非常相似,但只適用於函式元件,而不適用 class 元件。
const MyComponent = function MyComponent(props) {
/* 使用 props 渲染 */
};
export default React.memo(MyComponent)
如果你的函式元件在給定相同 props 的情況下渲染相同的結果,那麼你可以透過將其包裝在 React.memo 中呼叫,以此透過記憶元件渲染結果的方式來提高元件的效能表現。這意味著在這種情況下,React 將跳過渲染元件的操作並直接複用最近一次渲染的結果,即元件僅在它的 props 發生改變的時候進行重新渲染。通常來說,在元件樹中 React 元件,只要有變化就會走一遍渲染流程。但是 React.memo(),我們可以僅僅讓某些元件進行渲染。
React.memo 僅檢查 props 變更。如果函式元件被 React.memo 包裹,且其實現中擁有 useState 或 useContext 的 Hook,當 context 發生變化時,它仍會重新渲染。
預設情況下其只會對複雜物件做淺層對比,如果你想要控制對比過程,那麼請將自定義的比較函式透過第二個引數傳入來實現。
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
}
export default React.memo(MyComponent, areEqual);
示例:
// 子元件程式碼:
import React, { memo } from 'react';
const Child = ()=>{
console.log("2. 子元件渲染了")
return (<div>子元件</div>)
}
export default Child
// 父元件程式碼:
import React, { memo } from 'react';
import Child from './Child.jsx'
const Father = ()=>{
const [name,setName]=React.useState('');
console.log("1. 父元件渲染了")
return (<div>
/* 在input框中輸入內容,會走setName導致App元件重新渲染,但是子元件Child也會進行渲染。 */
父元件:<input type="text" value={name} onChange={ev=>setName(ev.target.value)} />
<Child />
</div>)
}
// 子元件程式碼:
import React, { memo } from 'react';
const Child = ()=>{
console.log("2. 子元件渲染了")
return (<div>子元件</div>)
}
export default memo(Child)
// 父元件程式碼:
import React, { memo } from 'react';
import Child from './Child.jsx'
const Father = ()=>{
const [name,setName]=React.useState('');
console.log("1. 父元件渲染了")
return (<div>
/* 解決:子元件使用memo包起來 */
父元件:<input type="text" value={name} onChange={ev=>setName(ev.target.value)} />
<Child />
</div>)
}