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/
