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/