求上進的人,不要總想著靠誰,人都是自私的,自己才是最靠得住的人。 React 中生命周期劃時代幾個節點,React 16.2 之前處於老的生命周期,之後提出了新的生命周期。而函數式組件在 React 16.8 之前是沒有狀態和生命周期的,在 React 16.8 版本通過引入 Hooks 使得函數式 ...
求上進的人,不要總想著靠誰,人都是自私的,自己才是最靠得住的人。
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>)
}