父子組件生命周期:
“生命周期”細想之下有點浪漫主義色彩,不知道是不是從lifecycle英譯過來的。作為一個前端從業者,如果讓我來取,可能會取成“渲染周期”之類的,畢竟是和瀏覽器打交道的職業,瀏覽器的layout使dom樹具有骨架,paint則讓整個頁面光亮起來。
React 的一切都是組件,通過 React.createElement 方法來創建嵌套層級,說白了在內存中構建對象樹,據此渲染到瀏覽器中成為dom樹,這個時候一個節點是什么時候真正渲染到頁面中就變得重要起來,因為只有這個時候你才能真正和瀏覽器環境內的對象和方法交互,同樣離開的時候也需要清理監聽器等防止干擾后續邏輯,因此鈎子函數,也可以說是生命周期函數就有了存在的意義。
先上代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Document</title> <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> </head> <body> <div id="app"></div> <div id="hollow"></div> <script type="text/babel"> const { Component, Fragment } = React; class F extends Component { state = { x: 'state before' }; static getDerivedStateFromProps(getProps, getState) { console.log('F get props', { getProps, getState }); } componentDidMount() { console.log('F did mount'); setTimeout(() => { console.log('%c %s', 'color:blue', 'start to update state'); this.setState({ x: 'state after' }); }, 2000); } shouldComponentUpdate(nextProps, nextState) { console.log('F should update', { nextProps, nextState }); return true; } componentDidUpdate(prevProps, prevState) { console.log('F did update', { prevProps, prevState }); } componentWillUnmount() { console.log('F will unmount', Date.now()); } render() { return ( <div> {this.props.x} {this.state.x} </div> ); } } class App extends Component { state = { x: 'props before ' }; componentDidMount() { console.log('App did mount'); setTimeout(() => { console.log('%c %s', 'color:red', 'start to update props'); this.setState({ x: 'props after ' }); }, 2000); } componentWillUnmount() { console.log('App will unmount', Date.now()); } render() { return <F {...this.state} />; } } setTimeout(() => { ReactDOM.render('unmount Component App at ' + Date.now(), app); }, 6000); ReactDOM.render(<App />, app); </script> </body> </html>
Props 和 State 相關
父組件 App 將自身的State傳入了子組件 F 內,忽略掛載和卸載,列舉生命周期函數:
1,getDerivedStateFromProps --
通過瀏覽器打印結果可以看到,不管是組件初始化,還是更新或繼承新的 state 或者 props ,最先觸發的都是靜態鈎子函數 getDerivedStateFromProps ;
2,shouldComponentUpdate --
在 setState 異步賦值了 state 的下一個狀態后,整個子組件 F 開始收集和對比新舊狀態,將新的狀態輸入到生命周期函數 shouldComponentUpdate 中,寫明 return 的值是 truthy 還是 falsy 可以選擇中斷更新或者繼續更新;
3,componentDidUpdate --
在 shouldComponentUpdate 順利進入下一步后,將執行 render 方法,更新虛擬dom樹,瀏覽器完成渲染,在此之后舊的狀態將作為參數傳給 componentDidUpdate ,可以對比新狀態以及是否保留舊狀態;
掛載 和 卸載
componentDidMount 和 componentWillUnmount --
觀察父子組件的掛載生命周期函數,可以發現掛載時,子組件的掛載鈎子先被觸發;卸載時,子組件的卸載鈎子后被觸發;
對於掛載鈎子,一般來說,應該將子組件從上至下依次掛載到一個 fragment 上,再整體掛載到dom樹中,因為頻繁操作dom樹不僅影響性能甚至可能影響用戶體驗。
但是實際情況卻並如此,我們經常在掛載函數上注冊監聽器,說明此時是可以與頁面交互的,也就是說其實所有掛載鈎子都是在父組件實際掛載到dom樹上才觸發的,不過是在父組件掛載后依次觸發子組件的 componentDidmount ,最后再觸發自身的掛載鈎子,說白了,componentDidMount 其實是異步鈎子。
相反,卸載的時候父節點先被移除,再從上至下依次觸發子組件的卸載鈎子;
但是我們也經常在卸載鈎子上卸載監聽器,這說明 componentWillUnmount 其實在父組件從dom樹上卸載前觸發的,先觸發自身的卸載鈎子,但此時並未從dom樹上剝離,然后依次嘗試觸發所有子組件的卸載鈎子,最后,父組件從dom樹上完成實際卸載。