在學習vue時候有生命週期的認知,其實react一樣也有生命週期。 React的生命週期組件分三個階段:
- Mounting(加載階段)
- Updating(更新階段)
- Unmounting(卸載階段)
舊生命週期
版本更新我們先看下之前舊的生命週期
以一個例子爲例
import React, { Component } from 'react'
class LifeCycleSon extends Component {
constructor(props){
super(props)
this.state={
word: '我是生命週期內的state'
}
console.log('1.constructor構造函數')
}
componentWillMount(){
//組件將要掛在,這個時候我們可以進行api的調用,獲取數據,但是不能進行dom操作
console.log('2.componentWillMount組件將要掛載')
}
componentDidMount(){
//此時組件已經掛載,我們可以進行dom操作,可以對我們的狀態進行更新操作了
console.log('4.componentDidMount組件已經掛載')
}
componentWillReceiveProps(nextProps){
//父組件傳遞的屬性有變化,我們可以在這裏做相應的響應操作
console.log("5.componentWillReceiveProps父組件傳遞的屬性更新了")
}
shouldComponentUpdate(nextProps, nextState){
//組件是否需要更新,需要返回一個布爾值,返回true則更新,返回flase不更新,這是一個優化點
console.log('6.shouldComponentUpdate組件是否應該更新,需要返回布爾值')
return true
}
componentWillUpdate(nextProps, nextState){
//組件將要更新
console.log('7.componentWillUpdate組件將要更新')
}
componentDidUpdate(){
//組件已經更新
console.log('8.componentDidUpdate組件已經更新')
}
componentWillUnmount(){
//組件銷燬
console.log("9.componentWillUnmount組件已經銷燬")
}
render() {
console.log('3.render組件渲染')
return (
<div>
{this.props.title}
</div>
)
}
}
export default class LifeCycle extends Component {
constructor(props){
super(props)
this.state={
son:'我是生命週期父組件',
showSon:true
}
setTimeout(()=>{
this.setState({
son:'父組件更新',
})
},2000)
setTimeout(()=>{
this.setState({
showSon:false
})
},4000)
}
render() {
return (
<div>
{this.state.showSon?<LifeCycleSon title={this.state.son}></LifeCycleSon>:<div>組件已銷燬</div>}
</div>
)
}
}
Mounting(加載階段:涉及6個鉤子函數)
constructor()
加載的時候調用一次,可以初始化state
getDefaultProps()
設置默認的props,也可以用dufaultProps設置組件的默認屬性。
getInitialState()
初始化state,可以直接在constructor中定義this.state
componentWillMount()
組件加載時只調用,以後組件更新不調用,整個生命週期只調用一次,此時可以修改state
render()
react最重要的步驟,創建虛擬dom,進行diff算法,更新dom樹都在此進行。根據組件的props和state(無兩者的重傳遞和重賦值,論值是否有變化,都可以引起組件重新render) ,return 一個React元素(描述組件,即UI),不負責組件實際渲染工作,之後由React自身根據此元素去渲染出頁面DOM。render是純函數(Pure function:函數的返回結果只依賴於它的參數;函數執行過程裏面沒有副作用),不能在裏面執行this.setState,會有改變組件狀態的副作用。
componentDidMount()
組件渲染之後調用,只調用一次
上述的代碼掛載結果爲
Updating(更新階段:涉及5個鉤子函數)
componentWillReceivePorps(nextProps)
組件加載時不調用,組件接受新的props時調用,所以在此方法中根據nextProps和this.props來查明重傳的props是否改變,以及如果改變了要執行啥,比如根據新的props調用this.setState出發當前組件的重新render。根據官網的描述:在該函數(componentWillReceiveProps)中調用 this.setState() 將不會引起第二次渲染。
shouldComponentUpdate(nextProps, nextState)
組件接收到新的props或者state時調用,return true就會更新dom(使用diff算法更新),return false能阻止更新(不調用render)以此可用來減少組件的不必要渲染,優化組件性能。
ps:這邊也可以看出,就算componentWillReceiveProps()中執行了this.setState,更新了state,但在render前(如shouldComponentUpdate,componentWillUpdate),this.state依然指向更新前的state,不然nextState及當前組件的this.state的對比就一直是true了。
componentWillUpdata(nextProps, nextState)
組件加載時不調用,只有在組件將要更新時才調用,此時可以修改state,在這邊可執行一些組件更新發生前的工作,一般較少用。
render()
render方法在上文講過,這邊只是重新調用。
componentDidUpdate(prevProps, prevState)
組件加載時不調用,組件更新完成後調用,可以操作組件更新的DOM,prevProps和prevState這兩個參數指的是組件更新前的props和state
上述的代碼更新結果爲
造成組件更新的情況
- 父組件重新render
a. 直接使用,每當父組件重新render導致的重傳props,子組件將直接跟着重新渲染,無論props是否有變化。可通過shouldComponentUpdate方法優化。
class Child extends Component {
shouldComponentUpdate(nextProps){ // 應該使用這個方法,否則無論props是否有變化都將會導致組件跟着重新渲染
if(nextProps.someThings === this.props.someThings){
return false
}
}
render() {
return <div>{this.props.someThings}</div>
}
}
b.在componentWillReceiveProps方法中,將props轉換成自己的state
class Child extends Component {
constructor(props) {
super(props);
this.state = {
someThings: props.someThings
};
}
componentWillReceiveProps(nextProps) { // 父組件重傳props時就會調用這個方法
this.setState({someThings: nextProps.someThings});
}
render() {
return <div>{this.state.someThings}</div>
}
}
- 組件本身調用setState,無論state有沒有變化。可通過shouldComponentUpdate方法優化。
class Child extends Component {
constructor(props) {
super(props);
this.state = {
someThings:1
}
}
shouldComponentUpdate(nextStates){ // 應該使用這個方法,否則無論state是否有變化都將會導致組件重新渲染
if(nextStates.someThings === this.state.someThings){
return false
}
}
handleClick = () => { // 雖然調用了setState ,但state並無變化
const preSomeThings = this.state.someThings
this.setState({
someThings: preSomeThings
})
}
render() {
return <div onClick = {this.handleClick}>{this.state.someThings}</div>
}
}
Unmounting(卸載階段:涉及1個鉤子函數)
componentWillUnmount()
組件渲染之後調用,只調用一次。此方法在組件被卸載前調用,可以在這裏執行一些清理工作,比如清楚組件中使用的定時器,清楚componentDidMount中手動創建的DOM元素等,以避免引起內存泄漏。
上述的代碼銷燬結果爲
新生命週期v16.4
官網鏈接:
http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate
import React, { Component } from 'react'
class LifeCycleSon extends Component {
constructor(props){
super(props)
// getDefaultProps:接收初始props
// getInitialState:初始化state
this.state={
word: '我是生命週期內的state'
}
console.log('1.constructor構造函數')
}
static getDerivedStateFromProps(nextProps, prevState){
console.log('2.getDerivedStateFromProps-----',nextProps,prevState)
return nextProps
}
componentDidCatch(error, info) { // 獲取到javascript錯誤
console.log('componentDidCatch',error, info)
}
componentDidMount(){
//此時組件已經掛載,我們可以進行dom操作,可以對我們的狀態進行更新操作了
console.log('4.componentDidMount組件已經掛載')
}
shouldComponentUpdate(nextProps, nextState){
//組件是否需要更新,需要返回一個布爾值,返回true則更新,返回flase不更新,這是一個優化點
console.log('6.shouldComponentUpdate組件是否應該更新,需要返回布爾值',nextProps, nextState)
return true
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 組件更新前觸發
console.log('7.getSnapshotBeforeUpdate',prevProps, prevState)
return null
}
componentDidUpdate(prevProps, prevState, snapshot){
//組件已經更新
console.log('8.componentDidUpdate組件已經更新',prevProps, prevState, snapshot)
}
componentWillUnmount(){
//組件銷燬
console.log("9.componentWillUnmount組件已經銷燬")
}
render() {
console.log('3.render組件渲染')
return (
<div>
{this.props.title}
</div>
)
}
}
export default class LifeCycle extends Component {
constructor(props){
super(props)
this.state={
son:'我是生命週期父組件',
showSon:true
}
setTimeout(()=>{
this.setState({
son:'父組件更新',
})
},2000)
setTimeout(()=>{
this.setState({
showSon:false
})
},4000)
}
render() {
return (
<div>
{this.state.showSon?<LifeCycleSon title={this.state.son}></LifeCycleSon>:<div>組件已銷燬</div>}
</div>
)
}
}
Mounting(加載階段:涉及6個鉤子函數)
當組件實例被創建並插入 DOM 中時,其生命週期調用順序如下:
constructor()
加載的時候調用一次,可以初始化state
static getDerivedStateFromProps(nextProps, prevState)
用一個靜態函數getDerivedStateFromProps來取代被deprecate的幾個生命週期函數,就是強制開發者在render之前只做無副作用的操作,而且能做的操作侷限在根據props和state決定新的state,而已。組件每次被rerender的時候,包括在組件構建之後(虛擬dom之後,實際dom掛載之前),每次獲取新的props或state之後;每次接收新的props之後都會返回一個對象作爲新的state,返回null則說明不需要更新state;配合componentDidUpdate,可以覆蓋componentWillReceiveProps的所有用法。
注意:
- getDerivedStateFromProps前面要加上static保留字,聲明爲靜態方法,不然會被react忽略掉
- getDerivedStateFromProps裏面的this爲undefined。static靜態方法只能Class(構造函數)來調用(App.staticMethod✅),而實例是不能的( (new App()).staticMethod ❌ );當調用React Class組件時,改組件會實例化;所以React Class組件中,靜態方法getDerivedStateFromProps無權訪問Class實例的this,即this爲undefined。可以看react issue相關討論 https://github.com/facebook/react/issues/12612 https://github.com/facebook/react/issues/14730
render()
react最重要的步驟,創建虛擬dom,進行diff算法,更新dom樹都在此進行,和上面無差
componentDidMount()
組件渲染之後調用,只調用一次
結果爲:
Updating(更新階段:涉及5個鉤子函數)
static getDerivedStateFromProps(props, state)
組件每次被rerender的時候,包括在組件構建之後(虛擬dom之後,實際dom掛載之前),每次獲取新的props或state之後;每次接收新的props之後都會返回一個對象作爲新的state,返回null則說明不需要更新state;配合componentDidUpdate,可以覆蓋componentWillReceiveProps的所有用法
shouldComponentUpdate(nextProps, nextState)
組件接收到新的props或者state時調用,return true就會更新dom(使用diff算法更新),return
false能阻止更新(不調用render)
render()
react最重要的步驟,創建虛擬dom,進行diff算法,更新dom樹都在此進行
getSnapshotBeforeUpdate(prevProps, prevState)
觸發時間update發生的時候,在render之後,在組件dom渲染之前;返回一個值,作爲componentDidUpdate的第三個參數;配合componentDidUpdate,
可以覆蓋componentWillUpdate的所有用法
例如官方示例
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 我們是否在 list 中添加新的 items ?
// 捕獲滾動位置以便我們稍後調整滾動位置。
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 如果我們 snapshot 有值,說明我們剛剛添加了新的 items,
// 調整滾動位置使得這些新 items 不會將舊的 items 推出視圖。
//(這裏的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
componentDidUpdate()
組件加載時不調用,組件更新完成後調用
結果:
Unmounting(卸載階段:涉及1個鉤子函數)
componentWillUnmount()
組件渲染之後調用,只調用一次。此方法在組件被卸載前調用,可以在這裏執行一些清理工作,比如清楚組件中使用的定時器,清楚componentDidMount中手動創建的DOM元素等,以避免引起內存泄漏。
上述的代碼銷燬結果爲
componentDidCatch(error,info)
組件渲染之後調用,只調用一次
參考鏈接
https://zhuanlan.zhihu.com/p/38030418
https://www.jianshu.com/p/50fe3fb9f7c3