React生命週期
React
的生命週期從廣義上分為掛載、渲染、解除安裝三個階段,在React
的整個生命週期中提供很多鉤子函式在生命週期的不同時刻呼叫。
描述
此處描述的是使用class
類元件提供的生命週期函式,每個元件都包含自己的生命週期方法,通過重寫這些方法,可以在執行過程中特定的階段執行這些方法,常用的生命週期有constructor()
、render()
、componentDidMount()
、componentDidUpdate()
、componentWillUnmount()
。
掛載過程
當元件例項被建立並插入DOM
中時,其生命週期呼叫順序如下:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
在這個階段的componentWillMount()
生命週期即將過時,在新程式碼中應該避免使用。
更新過程
當元件的props
或state
發生變化時會觸發更新,元件更新的生命週期呼叫順序如下:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
在這個階段的componentWillUpdate()
、componentWillReceiveProps()
生命週期即將過時,在新程式碼中應該避免使用。
解除安裝過程
當元件從DOM
中移除時,元件更新的生命週期呼叫順序如下:
componentWillUnmount()
錯誤處理
當渲染過程,生命週期,或子元件的建構函式中丟擲錯誤時,會呼叫如下方法:
static getDerivedStateFromError()
componentDidCatch()
生命週期
constructor()
在React
元件掛載之前,會呼叫它的建構函式,如果不初始化state
或不進行方法繫結,則不需要為React
元件實現建構函式。在為React.Component
子類實現建構函式時,應在其他語句之前前呼叫super(props)
,否則this.props
在建構函式中可能會出現未定義的錯誤。
通常在React
中建構函式僅用於以下兩種情況:
- 通過給
this.state
賦值物件來初始化內部state
。 - 為事件處理函式繫結例項。
constructor(props) {
super(props);
}
static getDerivedStateFromProps()
getDerivedStateFromProps
靜態方法會在呼叫render
方法之前呼叫,並且在初始掛載及後續更新時都會被呼叫,它應返回一個物件來更新state
,如果返回null
則不更新任何內容。此方法無權訪問元件例項,如果確實需要,可以通過提取元件props
的純函式及class
之外的狀態,在getDerivedStateFromProps()
和其他class
方法之間重用程式碼。此外,不管原因是什麼,都會在每次渲染前觸發此方法。
static getDerivedStateFromProps(props, state) {}
render()
render()
方法是class
元件中唯一必須實現的方法,render()
函式應該為純函式,這意味著在不修改元件state
的情況下,每次呼叫時都返回相同的結果,並且它不會直接與瀏覽器互動。如需與瀏覽器進行互動,請在componentDidMount()
或其他生命週期方法中執行操作,保持render()
為純函式。當render
被呼叫時,它會檢查this.props
和this.state
的變化並返回以下型別之一:
React
元素,通常通過JSX
建立,例如<div />
會被React
渲染為DOM
節點,<MyComponent />
會被React
渲染為自定義元件,無論是<div />
還是<MyComponent />
均為React
元素。- 陣列或
fragments
,使得render
方法可以返回多個元素。 Portals
,可以渲染子節點到不同的DOM
子樹中。- 字串或數值型別,它們在
DOM
中會被渲染為文字節點。 - 布林型別或
null
,什麼都不渲染,主要用於支援返回test && <Child />
的模式,其中test
為布林型別。
render() {}
componentDidMount()
componentDidMount()
會在元件掛載後(即插入DOM
樹後)立即呼叫,依賴於DOM
節點的初始化應該放在這裡,如需通過網路請求獲取資料,此處是例項化請求的好地方。這個方法是比較適合新增訂閱的地方,如果新增了訂閱,請不要忘記在componentWillUnmount()
裡取消訂閱。
你可以在componentDidMount()
裡直接呼叫setState()
,它將觸發額外渲染,但此渲染會發生在瀏覽器更新螢幕之前,如此保證了即使在render()
兩次呼叫的情況下,使用者也不會看到中間狀態,請謹慎使用該模式,因為它會導致效能問題。通常應該在constructor()
中初始化state
,如果你的渲染依賴於DOM
節點的大小或位置,比如實現modals
和tooltips
等情況下,你可以使用此方式處理。
componentDidMount() {}
shouldComponentUpdate()
當props
或state
發生變化時,shouldComponentUpdate()
會在渲染執行之前被呼叫,返回值預設為true
,首次渲染或使用forceUpdate()
時不會呼叫該方法。根據shouldComponentUpdate()
的返回值,判斷React
元件的輸出是否受當前state
或props
更改的影響。預設行為是state
每次發生變化元件都會重新渲染,大部分情況下,你應該遵循預設行為。
此方法僅作為效能優化的方式而存在,不要企圖依靠此方法來阻止渲染,因為這可能會產生bug
,你應該考慮使用內建的PureComponent
元件,而不是手動編寫shouldComponentUpdate()
,PureComponent
會對props
和state
進行淺層比較,並減少了跳過必要更新的可能性。
如果你一定要手動編寫此函式,可以將this.props
與nextProps
以及this.state
與nextState
進行比較,並返回false
以告知React
可以跳過更新。請注意,返回false
並不會阻止子元件在state
更改時重新渲染。不建議在shouldComponentUpdate()
中進行深層比較或使用JSON.stringify()
,這樣非常影響效率,且會損害效能。目前如果shouldComponentUpdate()
返回false
,則不會呼叫UNSAFE_componentWillUpdate()
,render()
和componentDidUpdate()
。後續版本React
可能會將shouldComponentUpdate
視為提示而不是嚴格的指令,並且當返回false
時仍可能導致元件重新渲染。
shouldComponentUpdate(nextProps, nextState) {}
getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate()
在最近一次渲染輸出(提交到DOM
節點)之前呼叫,它使得元件能在發生更改之前從DOM
中捕獲一些資訊(例如滾動位置),此生命週期的任何返回值將作為引數傳遞給componentDidUpdate()
,該方法應返回snapshot
的值或null
。
此用法並不常見,但它可能出現在UI
處理中,如需要以特殊方式處理滾動位置的聊天執行緒等。
getSnapshotBeforeUpdate(prevProps, prevState) {}
componentDidUpdate()
componentDidUpdate()
會在更新後會被立即呼叫,首次渲染不會執行此方法。當元件更新後,可以在此處對DOM
進行操作,如果你對更新前後的props
進行了比較,也可以選擇在此處進行網路請求(例如,當props
未發生變化時,則不會執行網路請求。如果shouldComponentUpdate()
返回值為false
,則不會呼叫componentDidUpdate()
。
你也可以在componentDidUpdate()
中直接呼叫setState()
,但請注意它必須被包裹在一個條件語句裡,否則會導致死迴圈,因為他將無限次觸發componentDidUpdate()
。它還會導致額外的重新渲染,雖然使用者不可見,但會影響元件效能。
如果元件實現了getSnapshotBeforeUpdate()
生命週期(不常用),則它的返回值將作為componentDidUpdate()
的第三個引數snapshot
引數傳遞,否則此引數將為undefined
。
componentDidUpdate(prevProps, prevState, snapshot) {}
componentWillUnmount()
componentWillUnmount()
會在元件解除安裝及銷燬之前直接呼叫,在此方法中執行必要的清理操作,例如清除timer
、取消網路請求或清除在componentDidMount()
中建立的訂閱等。
componentWillUnmount()
中不應呼叫setState()
,因為該元件將永遠不會重新渲染,元件例項解除安裝後,將永遠不會再掛載它。
componentWillUnmount() {}
static getDerivedStateFromError()
此生命週期會在後代元件丟擲錯誤後被呼叫,它將丟擲的錯誤作為引數,並返回一個值以更新state
。getDerivedStateFromError()
會在渲染階段呼叫,因此不允許出現副作用,如遇此類情況,請改用componentDidCatch()
。
static getDerivedStateFromError(error) {}
componentDidCatch()
此生命週期在後代元件丟擲錯誤後被呼叫,componentDidCatch()
會在提交階段被呼叫,因此允許執行副作用,它應該用於記錄錯誤之類的情況它接收兩個引數:
error
: 丟擲的錯誤。info
: 帶有componentStack key
的物件,其中包含有關元件引發錯誤的棧資訊。
componentDidCatch(error, info) {}
示例
React
元件的常用生命週期示例。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React生命週期</title>
</head>
<body>
<div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
componentDidMount() {
console.log("ComponentDidMount", this);
console.log(this.props);
console.log(this.state);
console.log("");
this.timer = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
console.log("ComponentWillUnmount", this);
console.log(this.props);
console.log(this.state);
console.log("");
clearInterval(this.timer);
}
tick() {
this.setState({ date: new Date() });
}
render() {
return (
<div>
<h1>{this.props.tips}</h1>
<h2>Now: {this.state.date.toLocaleTimeString()}</h2>
</div>
);
}
}
class App extends React.Component{
constructor(props){
super(props);
this.state = {
showClock: true,
tips: "Hello World!"
}
}
componentDidUpdate(prevProps, prevState) {
console.log("ComponentDidUpdate", this);
console.log(this.props);
console.log(this.state);
console.log("");
}
updateTips() {
this.setState((state, props) => ({
tips: "React update"
}));
}
changeDisplayClock() {
this.setState((state, props) => ({
showClock: !this.state.showClock
}));
}
render() {
return (
<div>
{this.state.showClock && <Clock tips={this.state.tips} />}
<button onClick={() => this.updateTips()}>更新tips</button>
<button onClick={() => this.changeDisplayClock()}>改變顯隱</button>
</div>
);
}
}
var vm = ReactDOM.render(
<App />,
document.getElementById("root")
);
</script>
</html>
每日一題
https://github.com/WindrunnerMax/EveryDay
參考
https://www.jianshu.com/p/b331d0e4b398
https://www.cnblogs.com/soyxiaobi/p/9559117.html
https://zh-hans.reactjs.org/docs/react-component.html
https://zh-hans.reactjs.org/docs/state-and-lifecycle.html
https://www.runoob.com/react/react-component-life-cycle.html
https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/