轉載:https://segmentfault.com/a/1190000016617400
首先來列舉一下react的生命周期(react16版本)
Mounting(加載階段:涉及4個鉤子函數)
1.constructor()
加載的時候調用一次,可以初始化state
2.static getDerivedStateFromProps(props, state)
組件每次被rerender的時候,包括在組件構建之后(虛擬dom之后,實際dom掛載之前),每次獲取新的props或state之后;每次接收新的props之后都會返回一個對象作為新的state,返回null則說明不需要更新state;配合componentDidUpdate,可以覆蓋componentWillReceiveProps的所有用法
3.render()
react最重要的步驟,創建虛擬dom,進行diff算法,更新dom樹都在此進行
4.componentDidMount()
組件渲染之后調用,只調用一次
Updating(更新階段:涉及5個鉤子函數)
1.static getDerivedStateFromProps(props, state)
組件每次被rerender的時候,包括在組件構建之后(虛擬dom之后,實際dom掛載之前),每次獲取新的props或state之后;每次接收新的props之后都會返回一個對象作為新的state,返回null則說明不需要更新state;配合componentDidUpdate,可以覆蓋componentWillReceiveProps的所有用法
2.shouldComponentUpdate(nextProps, nextState)
組件接收到新的props或者state時調用,return true就會更新dom(使用diff算法更新),return false能阻止更新(不調用render)
3.render()
react最重要的步驟,創建虛擬dom,進行diff算法,更新dom樹都在此進行
4.getSnapshotBeforeUpdate(prevProps, prevState)
觸發時間: update發生的時候,在render之后,在組件dom渲染之前;返回一個值,作為componentDidUpdate的第三個參數;配合componentDidUpdate, 可以覆蓋componentWillUpdate的所有用法
5.componentDidUpdate()
組件加載時不調用,組件更新完成后調用
Unmounting(卸載階段:涉及1個鉤子函數)
1.componentWillUnmount()
Error Handling(錯誤處理)
1.componentDidCatch(error,info)
任何一處的javascript報錯會觸發
組件的基本寫法
import React, { Component } from 'react'
export default class NewReactComponent extends Component {
constructor(props) {
super(props)
// getDefaultProps:接收初始props
// getInitialState:初始化state
}
state = {
}
static getDerivedStateFromProps(props, state) { // 組件每次被rerender的時候,包括在組件構建之后(虛擬dom之后,實際dom掛載之前),每次獲取新的props或state之后;;每次接收新的props之后都會返回一個對象作為新的state,返回null則說明不需要更新state
return state
}
componentDidCatch(error, info) { // 獲取到javascript錯誤
}
render() {
return (
<h2>New React.Component</h2>
)
}
componentDidMount() { // 掛載后
}
shouldComponentUpdate(nextProps, nextState) { // 組件Props或者state改變時觸發,true:更新,false:不更新
return true
}
getSnapshotBeforeUpdate(prevProps, prevState) { // 組件更新前觸發
return null;
}
componentDidUpdate() { // 組件更新后觸發
}
componentWillUnmount() { // 組件卸載時觸發
}
}
新的生命周期
1.React16新的生命周期棄用了componentWillMount、componentWillReceiveProps,componentWillUpdate
2.新增了getDerivedStateFromProps、getSnapshotBeforeUpdate來代替棄用的三個鉤子函數(componentWillMount、componentWillReceiveProps,componentWillUpdate)
3.React16并沒有刪除這三個鉤子函數,但是不能和新增的鉤子函數(getDerivedStateFromProps、getSnapshotBeforeUpdate)混用,React17將會刪除componentWillMount、componentWillReceiveProps,componentWillUpdate
4.新增了對錯誤的處理(componentDidCatch)
了解了生命周期,下面來說一說生命周期的執行順序
1.組件生命周期的執行次數
1.只執行一次: constructor、componentDidMount
2.執行多次:render、getDerivedStateFromProps、shouldComponentUpdate、getSnapshotBeforeUpdate、componentDidUpdate
3.有條件執行:componentWillUnmount
1.組件的生命周期執行順序
假設組件嵌套關系parent組件中有child組件
不更新dom執行順序如下
更新dom執行順序如下
修改父組件的state
修改子組件的state
結論
1.完成前的順序是從根部到子部,完成時是從子部到根部。(類似于事件機制)
2.子組件setState是不能觸發其父組件的生命周期更新函數,只能觸發更低一級別的生命周期更新函數。
setState在生命周期中的使用注意事項
1、僅當子組件的props發生變化時getDerivedStateFromProps生命鉤子才會被觸發。該生命周期會有一個參數nextProps,表示子組件被更新后的props。因此可以在該周期獲取最新的props在通過setState更新組件狀態。
2、子組件props或state更新都會觸發shouldComponentUpdate生命鉤子。該生命周期有兩個參數nextProps,nextState 表示更新后的props和更新后的state, 該生命周期是整提高組件性能的一個重要函數,它通過判斷當前狀態與之前狀態來返回一個布爾值并決定是否更新視圖,如果返回false視圖始終不會更新。返回true就會更新視圖
3、getSnapshotBeforeUpdate生命周期在shouldComponentUpdate返回true后被觸發。在這兩個生命周期只要視圖更新就會觸發,因此不能再這兩個生命周期中使用setState。否則會導致死循環。
4、componentDidUpdate生命周期在shouldComponentUpdate返回true后觸發。在此生命周期中setState會導致視圖再次更新,類似于componentDidMount,因此除非特殊業務需求,否則不建議在此生命周期中使用setState。
5、componentWillUnmount生命周期在組件被卸載后觸發,在此生命周期使用setState不會觸發。
以下文章來源于魔術師卡頌 ,作者卡頌
react render渲染條件
點擊Parent組件的div,觸發更新,Son組件會打印child render!么?
function Son() {
console.log('child render!');
return <div>Son</div>;
}
function Parent(props) {
const [count, setCount] = React.useState(0);
return (
<div onClick={() => {setCount(count + 1)}}>
count:{count}
{props.children}
</div>
);
}
function App() {
return (
<Parent>
<Son/>
</Parent>
);
}
const rootEl = document.querySelector("#root");
ReactDOM.render(<App/>, rootEl);
render需要滿足的條件
React創建Fiber樹時,每個組件對應的fiber都是通過如下兩個邏輯之一創建的:
render。即調用render函數,根據返回的JSX創建新的fiber。
bailout。即滿足一定條件時,React判斷該組件在更新前后沒有發生變化,則復用該組件在上一次更新的fiber作為本次更新的fiber。
可以看到,當命中bailout邏輯時,是不會調用render函數的。
所以,Son組件不會打印child render!是因為命中了bailout邏輯。
bailout需要滿足的條件
什么情況下會進入bailout邏輯?當同時滿足如下4個條件時:
- oldProps === newProps ?
即本次更新的props(newProps)不等于上次更新的props(oldProps)。
注意這里是全等比較。
我們知道組件render會返回JSX,JSX是React.createElement的語法糖。
所以render的返回結果實際上是React.createElement的執行結果,即一個包含props屬性的對象。
即使本次更新與上次更新props中每一項參數都沒有變化,但是本次更新是React.createElement的執行結果,是一個全新的props引用,所以oldProps !== newProps。
如果我們使用了PureComponent或Memo,那么在判斷是進入render還是bailout時,不會判斷oldProps與newProps是否全等,而是會對props內每個屬性進行淺比較。
- context沒有變化
即context的value沒有變化。
- workInProgress.type === current.type ?
更新前后fiber.type是否變化,比如div是否變為p。
- !includesSomeLane(renderLanes, updateLanes) ?
當前fiber上是否存在更新,如果存在那么更新的優先級是否和本次整棵fiber樹調度的優先級一致?
如果一致則進入render邏輯。
就我們的Demo來說,Parent是整棵樹中唯一能觸發更新的組件(通過調用setCount)。
所以Parent對應的fiber是唯一滿足條件4的fiber。
Demo的詳細執行邏輯
所以,Demo中Son進入bailout邏輯,一定是同時滿足以上4個條件。我們一個個來看。
條件2,Demo中沒有用到context,滿足。
條件3,更新前后type都為Son對應的函數組件,滿足。
條件4,Son本身無法觸發更新,滿足。
所以,重點是條件1。讓我們詳細來看下。
本次更新開始時,Fiber樹存在如下2個fiber
FiberRootNode
|
RootFiber
其中FiberRootNode是整個應用的根節點,RootFiber是調用ReactDOM.render創建的fiber。
首先,RootFiber會進入bailout的邏輯,所以返回的App fiber和更新前是一致的。
FiberRootNode
|
RootFiber
|
App fiber
由于App fiber是RootFiber走bailout邏輯返回的,所以對于App fiber,oldProps === newProps。并且bailout剩下3個條件也滿足。
所以App fiber也會走bailout邏輯,返回Parent fiber。
FiberRootNode
|
RootFiber
|
App fiber
|
Parent fiber
由于更新是Parent fiber觸發的,所以他不滿足條件4,會走render的邏輯。
接下來是關鍵
如果render返回的Son是如下形式:
<Son/>
會編譯為
React.createElement(Son, null)
執行后返回JSX。
由于props的引用改變,oldProps !== newProps。會走render邏輯。
但是在Demo中Son是如下形式:
{props.children}
其中,props.children是Son對應的JSX,而這里的props是App fiber走bailout邏輯后返回的。
所以Son對應的JSX與上次更新時一致,JSX中保存的props也就一致,滿足條件1。
可以看到,Son滿足bailout的所有條件,所以不會render。