React生命周期
我們這里所說(shuō)的生命周期是基于類式組件的,其中主要分為三個(gè)階段
- 掛載
- 更新
- 卸載
以上每一個(gè)階段內(nèi)都有自己的一系列在當(dāng)前生命周期中可被自動(dòng)執(zhí)行的生命周期函數(shù)(類似于vue的鉤子函數(shù))
掛載階段(初始化)
在掛載階段按照?qǐng)?zhí)行順序分別有以下幾個(gè)生命周期函數(shù)
-
constructor()
初始化整個(gè)組件的state 以及調(diào)用super(props),該方法只會(huì)執(zhí)行一次 -
componentWillMount()
掛載之前(已經(jīng)棄用,目前React18.2中依然還是可以使用,如果使用會(huì)報(bào)警告) -
render()
只返回需要渲染頁(yè)面結(jié)構(gòu),不要包含其它的業(yè)務(wù)邏輯 -
componentDidMount()
掛載之后,可以獲取到DOM節(jié)點(diǎn)并操作,服務(wù)器請(qǐng)求,啟用事件監(jiān)聽(tīng),調(diào)用 setState()等都可以在此函數(shù)中完成,該方法只會(huì)執(zhí)行一次
在組件代碼中表示如下
import React, { Component } from 'react'
export default class Mount extends Component {
//掛載階段第一個(gè)執(zhí)行的生命周期函數(shù)
constructor(props){
super(props)
console.log("1、初始化")
}
//掛載階段第二個(gè)執(zhí)行的生命周期函數(shù)
componentWillMount(){
console.log("2、掛載之前")
}
//掛載階段第三個(gè)執(zhí)行的生命周期函數(shù)
render() {
console.log("3、渲染頁(yè)面結(jié)構(gòu)")
return (
<div>
</div>
)
}
//掛載階段第四個(gè)執(zhí)行的生命周期函數(shù)
componentDidMount(){
console.log("4、掛載之后")
}
}
注意事項(xiàng):
在執(zhí)行過(guò)程中我們會(huì)發(fā)現(xiàn)如下情況
1、控制臺(tái)會(huì)報(bào)一個(gè)警告,這個(gè)警告主要是告訴我們關(guān)于componentWillMount函數(shù)的名稱在React18.x的版本中名字要做修改,如下
UNSAFE_componentWillMount(){ console.log("2、掛載之前") }
2、按照上面的方式修改之后又會(huì)又一個(gè)警告,告訴我們?cè)趪?yán)格模式下使用 UNSAFE_ 的寫法可以會(huì)導(dǎo)致提示代碼中存在錯(cuò)誤,這個(gè)可以把在入口文件index.js中設(shè)置的React.StrictMode刪除掉,就不會(huì)有警告了
3、如果在嚴(yán)格模式下,我們會(huì)發(fā)現(xiàn)我們的生命周期函數(shù)出了componentWillMount之外的其他三個(gè)全部都執(zhí)行了兩邊,把嚴(yán)格模式刪除掉即可
擴(kuò)展內(nèi)容:為什么會(huì)渲染兩次?
之所以這些生命周期函數(shù)會(huì)被渲染兩次的原因在于React.StrictMode嚴(yán)格模式所提供的好處之一,它幫助我們檢測(cè)生命周期函數(shù)在執(zhí)行渲染時(shí)的副作用,而嚴(yán)格模式下的檢測(cè)方式就是故意調(diào)用一些關(guān)鍵函數(shù)兩次,來(lái)幫助發(fā)現(xiàn)副作用
這種渲染兩次的行為會(huì)對(duì)性能造成一定影響,但是它只針對(duì)開發(fā)環(huán)境,在生產(chǎn)環(huán)境中不會(huì)發(fā)生渲染兩次的情況
更新階段
更新階段分為兩種情況,分別是props更新時(shí)和state更新時(shí),我們先來(lái)看state更新時(shí)會(huì)執(zhí)行的生命周期函數(shù)
state更新時(shí)按照?qǐng)?zhí)行順序會(huì)觸發(fā)如下函數(shù):
-
shouldComponentUpdate()
根據(jù)return的布爾值來(lái)決定是否開始更新數(shù)據(jù) -
componentWillUpdate()
準(zhǔn)備開始更新 -
render()
重新渲染頁(yè)面結(jié)構(gòu) -
componentDidUpdate()
更新之后
注意:
componentWillReceiveProps()
方法會(huì)把新更新props接收為參數(shù)- 如果
shouldComponentUpdate()
方法return的是false,則后面三個(gè)函數(shù)不會(huì)執(zhí)行,同時(shí),這個(gè)方法可以接收兩個(gè)參數(shù),分別是更新之后的state數(shù)據(jù)和更新之后的props數(shù)據(jù)
代碼演示:
import React, { Component } from 'react'
class Son extends Component {
state = {
str:"hoho"
}
componentWillReceiveProps(newProps){
console.log("0、當(dāng)接收到新的props",newProps)
}
shouldComponentUpdate(newProps,newState){
console.log("1、是否更新數(shù)據(jù)",newProps,newState);
return true
}
componentWillUpdate(){
console.log("2、準(zhǔn)備開始更新")
}
render(){
console.log("3、重新渲染頁(yè)面結(jié)構(gòu)")
return (
<div>
<button onClick={this.changeStr.bind(this)}>更新state</button>
</div>
)
}
componentDidUpdate(){
console.log("4、更新之后")
}
//制作一個(gè)修改state的方法觸發(fā)生命周期的更新階段
changeStr(){
this.setState({
str:"haha"
})
}
}
export default class Mount extends Component {
state = {
num:1
}
//修改父組件傳入子組件的num觸發(fā)componentWillReceiveProps
changeNum(){
this.setState({
num:1
})
}
render(){
return (
<div>
<button onClick={this.changeNum.bind(this)}>修改num</button>
<Son num={this.state.num}></Son>
</div>
)
}
}
注意事項(xiàng):
componentWillReceiveProps()
函數(shù)會(huì)報(bào)警告要替換成新的static getDerivedStateFromProps()
或者加上UNSAFE_前綴componentWillUpdate()
函數(shù)也會(huì)遇到上面掛載階段函數(shù)名修改的警告提示,把該函數(shù)的名稱也加上UNSAFE_ 前綴即可UNSAFE_componentWillUpdate(){ console.log("2、準(zhǔn)備開始更新") }
數(shù)據(jù)修改過(guò)程做性能優(yōu)化
在上面的 shouldComponentUpdate()
函數(shù)中,我們知道它會(huì)被自動(dòng)注入兩個(gè)參數(shù),分別是修改后的props與修改后的state,而是否真的去做數(shù)據(jù)更新會(huì)根據(jù)這個(gè)函數(shù)的return值來(lái)決定,這里我們就可以手寫一個(gè)業(yè)務(wù)邏輯來(lái)對(duì)程序進(jìn)行性能優(yōu)化
業(yè)務(wù)邏輯如下:
如果修改之后的state值與修改之前的state值是一樣的,那我們就沒(méi)有必要專門再把數(shù)據(jù)更新一遍,因?yàn)檫@樣做會(huì)重新執(zhí)行一遍render渲染頁(yè)面結(jié)構(gòu)
把上面的業(yè)務(wù)邏輯轉(zhuǎn)換成代碼表示
shouldComponentUpdate(newProps,newState){
//newState接收的是一個(gè)state對(duì)象,修改的新值在對(duì)象的屬性中
if(newState.str === this.state.str){
return false
}else{
return true
}
}
上面的代碼過(guò)于冗余,做如下修改
shouldComponentUpdate(newProps,newState){
return newState.str !== this.state.str
}
props更新時(shí)會(huì)觸發(fā)以下生命周期函數(shù)執(zhí)行
props更新時(shí),所執(zhí)行的生命周期函數(shù)與執(zhí)行順序和state更新時(shí)一樣,只不過(guò)會(huì)在最前面添加一個(gè)生命周期函數(shù)的執(zhí)行
-
componentWillReceiveProps()
外部傳入的數(shù)據(jù)發(fā)生變化時(shí)觸發(fā)(父組件修改了一個(gè)自己組件內(nèi)部的數(shù)據(jù),而這個(gè)被修改的數(shù)據(jù)時(shí)有傳遞給子組件的) -
shouldComponentUpdate()
根據(jù)return的布爾值來(lái)決定是否接收外部傳入的新值 -
componentWillUpdate()
準(zhǔn)備開始更新 -
render()
重新渲染頁(yè)面結(jié)構(gòu) -
componentDidUpdate()
更新之后
**注意:**與上面的state更新時(shí)一樣,當(dāng)外部傳入的數(shù)據(jù)改變的時(shí)候,也會(huì)根據(jù)
shouldComponentUpdate
返回的布爾值來(lái)決定是否要接收在外部已經(jīng)被修改的新數(shù)據(jù),如果return false則后面三個(gè)函數(shù)不執(zhí)行
創(chuàng)建組件進(jìn)行代碼演示:
import React, { Component } from 'react'
class Child extends Component {
UNSAFE_componentWillReceiveProps(){
console.log("0、外部傳入的props被修改")
}
shouldComponentUpdate(newProps,newState){
console.log("1、是否要更新數(shù)據(jù)")
return true
}
UNSAFE_componentWillUpdate(){
console.log("2、準(zhǔn)備開始更新")
}
render(){
console.log("3、重新渲染頁(yè)面結(jié)構(gòu)")
return (
<div>
<h1>{this.props.cstr}</h1>
</div>
)
}
componentDidUpdate(){
console.log("4、更新之后")
}
}
//制作一個(gè)父組件
export default class Father extends Component {
state = {
fstr:"father"
}
changeStr(){
this.setState({
fstr:"haha"
})
}
render() {
return (
<div>
<button onClick={this.changeStr.bind(this)}>修改父組件的fstr</button>
<Child cstr={this.state.fstr}></Child>
</div>
)
}
}
把props的修改也考慮到數(shù)據(jù)修改的性能優(yōu)化上
如果props修改之后的值與修改之前的一樣,那么就不執(zhí)行更新
shouldComponentUpdate(newProps,newState){
return (newState.str !== this.state.str) || (newProps.cstr !== this.props.cstr)
}
PureComponent
PureComponent是React15.3版本新添加的一個(gè)繼承自Component的子類,當(dāng)我們的類組件繼承自PureComponent的時(shí)候,該組件會(huì)自動(dòng)加載shouldComponentUpdate聲明周期函數(shù),當(dāng)組件更新的時(shí)候會(huì)自動(dòng)對(duì)組件的props和state進(jìn)行新舊比較,如果沒(méi)有發(fā)生變化就不會(huì)觸發(fā)render方法讓組件二次渲染,就從可以達(dá)到和上面我們手動(dòng)編寫的優(yōu)化方法一樣的效果
import React, { Component,PureComponent } from 'react'
export default class Mount extends PureComponent {
//當(dāng)類組件繼承自PureComponent就相當(dāng)于自動(dòng)實(shí)現(xiàn)了,如下代碼
/*
shouldComponentUpdate(newProps,newState){
return (newState.str !== this.state.str) || (newProps.cstr !== this.props.cstr)
}
*/
}
卸載階段
卸載可以理解成到組件切換的時(shí)候,切換之前的組件會(huì)被卸載掉,在這個(gè)階段只有一個(gè)生命周期函數(shù)
1、componentWillUnmount()
當(dāng)組件卸載時(shí)觸發(fā)
創(chuàng)建組件進(jìn)行代碼演示:
import React, { Component } from 'react'
export default class Unmount extends Component {
render() {
return (
<div>
</div>
)
}
componentWillUnmount(){
console.log("當(dāng)組件卸載時(shí)")
}
}
然后,我們?cè)趇ndex.js中通過(guò)setTimeout制作對(duì)root中渲染的組件做一個(gè)延遲執(zhí)行
//......
import Unmount from './components/Unmount';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Unmount></Unmount>
);
//3秒之后渲染App組件,這個(gè)時(shí)候Unmount組件就會(huì)被替換掉從而觸發(fā)卸載生命周期函數(shù)
setTimeout(() => {
root.render(
<App></App>
)
},3000)
我們來(lái)通過(guò)一個(gè)具體的小例子來(lái)看待卸載生命周期函數(shù)的作用
上面的index.js中的內(nèi)容不動(dòng),依然模擬一個(gè)組件卸載的過(guò)程,然后把Unmount組件做一些修改
import React, { Component } from 'react'
export default class Unmount extends Component {
//到組件掛載之后給整個(gè)網(wǎng)頁(yè)文檔綁定一個(gè)點(diǎn)擊事件
componentDidMount(){
document.addEventListener("click",this.dbClick)
}
//制作一個(gè)觸發(fā)事件的方法
dbClick(){
console.log("我點(diǎn)擊了這個(gè)網(wǎng)頁(yè)")
}
render() {
return (
<div>
</div>
)
}
//到組件卸載的時(shí)候,把綁定在整個(gè)文檔上的事件移除掉
componentWillUnmount(){
console.log("當(dāng)組件卸載時(shí)")
document.removeEventListener("click",this.dbClick)
}
}
代碼分析:
上面的代碼主要情況就是,我們?cè)赨nmount組件掛載之后給全局綁定了一個(gè)點(diǎn)擊事件,所以就算你組件卸載了,這個(gè)事件也不會(huì)受影響,所以我們?cè)谛遁d的時(shí)候再次移除這個(gè)全局事件,從而保證雖然是一個(gè)全局事件,但是這個(gè)事件可以跟隨Unmount組件的生命周期來(lái)走
React16+新增生命周期函數(shù)
在上面介紹的生命周期函數(shù)中,有部分是在新版本中被棄用的,取而代之的也有一些新增的生命周期函數(shù)
getDerivedStateFromProps函數(shù)
這是一個(gè)靜態(tài)方法,所以在類組件中聲明時(shí)記得帶上static修飾符,它主要替代了掛載階段中componentWillMount
和更新階段中componentWillReceiveProps
這兩個(gè)函數(shù)的位置,也就是說(shuō) getDerivedStateFromProps
在在初始化和后續(xù)更新都會(huì)被調(diào)用
該方法會(huì)接收兩個(gè)參數(shù)
參數(shù)1:nextProps即將更新的props,父組件更新了傳入到子組件中的數(shù)據(jù)時(shí),更新后的數(shù)據(jù)會(huì)傳入此參數(shù)
參數(shù)2:prevState 子組件內(nèi)部的更新之前的狀態(tài)(舊的狀態(tài))
**返回值:**該方法會(huì)返回一個(gè)對(duì)象來(lái)更新組件的內(nèi)部狀態(tài)state,如果返回null則不更新
注意事項(xiàng):
使用該方法時(shí),一定要設(shè)置state,就算是個(gè)空對(duì)象都行
在介紹該方法的作用之前我們需要先介紹擴(kuò)展介紹一些知識(shí)點(diǎn)
派生狀態(tài)
在介紹
getDerivedStateFromProps
方法的使用之前我們要先理解一種特殊的組件內(nèi)部狀態(tài),派生狀態(tài),而介紹派生狀態(tài)之前我們還要簡(jiǎn)單復(fù)習(xí)并擴(kuò)展一下受控與非受控組件的內(nèi)容
之前我們有介紹過(guò)受控組件和非受控組件,“受控”和“不受控制”主要在我們之前的介紹中主要指的是表單數(shù)據(jù),但它還可以用于描述組件與組件之間的控制關(guān)系。作為props傳遞進(jìn)子組件的數(shù)據(jù)可以被認(rèn)為是受控的,所以我們認(rèn)為該子組件的數(shù)據(jù)受控與父組件。只存在于內(nèi)部狀態(tài)state的數(shù)據(jù)可以被認(rèn)為是不受控制的,因?yàn)檫@個(gè)數(shù)據(jù)不受父組件的控制。
舉例
import React, { Component } from 'react'
export default class Mount extends Component { //父組件
constructor(props){
super(props)
this.state={
textVal:"zhangsan"
}
}
changeVal(){
this.setState({
textVal:"lisi"
})
}
render(){
return (
<div>
<h2>父組件:{this.state.textVal}</h2>
<button onClick={this.changeVal.bind(this)}>修改父組件state按鈕</button>
<Son textVal={this.state.textVal} changeVal={this.changeVal.bind(this)}></Son>
</div>
)
}
}
class Son extends Component { //子組件
constructor(props){
super(props)
this.state = {
sonVal:this.props.textVal
}
}
changeVal(){
this.setState({
sonVal:"lisi"
})
}
render(){
return (
<div>
<h2>子組件state調(diào)用:{this.state.sonVal}</h2>
<h2>子組件props調(diào)用:{this.props.textVal}</h2>
<button onClick={this.changeVal.bind(this)}>修改子組件state按鈕</button>
</div>
)
}
}
代碼分析:
以上的情況我們可以認(rèn)為,父組件向子組件傳入了數(shù)據(jù)“zhangsan” ,但是在子組件中我們通過(guò)
sonVal:this.props.textVal
將該數(shù)據(jù)指派給了子組件自己的內(nèi)部狀態(tài)sonVal然后我們?cè)賮?lái)通過(guò)父組件Mount中修改狀態(tài)的方法changeVal修改父組件的狀態(tài)值時(shí),子組件中sonVal的值并不會(huì)跟著一起改變,這種情況下雖然子組件的sonVal的值是通過(guò)父組件傳入賦值得到的,但是它并不受控與父組件
而派生狀態(tài)的使用就是為了讓上面這種情況下的子組件中的sonVal依然還可以受控與父組件,也就是說(shuō)子組件的內(nèi)部狀態(tài)值會(huì)根據(jù)父組件傳入數(shù)據(jù)的改變而改變
注意點(diǎn):
派生狀態(tài)實(shí)現(xiàn)的功能與直接在子組件中通過(guò)props調(diào)用渲染父組件傳入的數(shù)據(jù)所形成的結(jié)果十分相似,但是其原理是不同的
- props的實(shí)現(xiàn)方式始終都沒(méi)有觸及到子組件自身的內(nèi)部狀態(tài),一致都使用的是父組件的數(shù)據(jù)
- 派生狀態(tài)的實(shí)現(xiàn)其本質(zhì)還是調(diào)用的組件自身的內(nèi)部狀態(tài),子組件通過(guò)props將父組件傳入的數(shù)據(jù)賦值給子組件的狀態(tài)
如何實(shí)現(xiàn)派生狀態(tài)
getDerivedStateFromProps這個(gè)方法的目的就只有一個(gè),就是實(shí)現(xiàn)派生狀態(tài),使組件可以根據(jù)props的結(jié)果來(lái)更新其內(nèi)部狀態(tài)
注意:謹(jǐn)慎使用派生狀態(tài)
舉例:
import React, { Component } from 'react'
export default class Father extends Component {
state = {
fatherVal: "hello"
};
ChangeVal(e){
this.setState({ fatherVal: e.target.value });
};
render(){
return (
<div>
父組件:<input type="text" value={this.state.fatherVal} onChange={this.ChangeVal.bind(this)} />
<Son textVal={this.state.fatherVal}></Son>
</div>
)
}
}
class Son extends Component {
state = {
sonVal:this.props.textVal
}
render() {
return <h2>子組件:{this.state.sonVal}</h2>;
}
static getDerivedStateFromProps(nextProps){
return {sonVal: nextProps.textVal}
}
// componentWillReceiveProps(nextProps) {
// this.setState({ sonVal: nextProps.textVal });
// }
}
代碼分析:
這里我們實(shí)現(xiàn)了派生狀態(tài),將father組件中的狀態(tài)fatherVal傳入Son組件,并指派給Son組件的內(nèi)部狀態(tài)sonVal,當(dāng)父組件觸發(fā)changeVal使父組件狀態(tài)發(fā)生改變觸發(fā)了組件更新進(jìn)行重新渲染,而這個(gè)渲染過(guò)程也會(huì)波及到子組件使得子組件也會(huì)一并更新,而子組件的更新也必然會(huì)觸發(fā)其生命周期函數(shù)
getDerivedStateFromProps
將父組件更新后的傳入子組件中的數(shù)據(jù)從props中調(diào)出再次賦值給子組件的內(nèi)部狀態(tài)
使用派生狀態(tài)遇到的常見(jiàn)bug
舉例:對(duì)上面的例子做一點(diǎn)修改,現(xiàn)在我們把子組件中的派生狀態(tài)sonVal渲染到一個(gè)表單中然后想讓該表單與sonVal實(shí)現(xiàn)雙向綁定
import React, { Component } from 'react'
export default class Father extends Component { //父組件內(nèi)部不變
state = {
fatherVal: "hello",
num:1
};
changeVal(e){
this.setState({ fatherVal: e.target.value });
};
render(){
return (
<div>
父組件:<input type="text" value={this.state.fatherVal} onChange={this.changeVal.bind(this)} />
<hr />
<Son textVal={this.state.fatherVal}></Son>
</div>
)
}
}
class Son extends Component {
state = {
sonVal:this.props.textVal,
}
//制作雙向綁定的方法
changeInput(e){
this.setState({sonVal:e.target.value})
}
render() {
return (
//把sonVal渲染把一個(gè)單表上,然后綁定onChange事件執(zhí)行雙向綁定
<div>
<h2>子組件:{this.state.sonVal}</h2>
<input type="text" value={this.state.sonVal} onChange={this.changeInput.bind(this)} />
</div>
)
}
static getDerivedStateFromProps(nextProps){
return {sonVal: nextProps.textVal}
}
// componentWillReceiveProps(nextProps) {
// this.setState({ sonVal: nextProps.textVal });
// }
}
代碼分析:
上面這樣寫是有問(wèn)題的,我們會(huì)發(fā)現(xiàn)這個(gè)input里面永遠(yuǎn)無(wú)法輸入新的值進(jìn)去
原因:
其實(shí)并不是寫不進(jìn)去數(shù)據(jù),而是每當(dāng)我們輸入一個(gè)數(shù)據(jù)的時(shí)候就會(huì)觸發(fā)組件的更新,而組件一更新就會(huì)重新渲染,而一旦重新渲染sonVal的值就又重新被指派成了父組件傳入的textVal的值,在input的value上面渲染的數(shù)據(jù)就又會(huì)變成父組件指派的數(shù)據(jù),周而復(fù)始,只要一直持續(xù)輸入新數(shù)據(jù)就一直這樣,導(dǎo)致在input中新輸入的數(shù)據(jù)一直被覆蓋掉,無(wú)法將新數(shù)據(jù)保留下來(lái)
解決方案:完全受控組件
直接全程props,從子組件中徹底刪除狀態(tài),子組件直接使用props不再去組件內(nèi)部接收父組件的指派狀態(tài)
import React, { Component } from 'react' export default class Father extends Component { state = { fatherVal: "hello", num:1 }; changeVal(e){ this.setState({ fatherVal: e.target.value }); }; render(){ return ( <div> 父組件:<input type="text" value={this.state.fatherVal} onChange={this.changeVal.bind(this)} /> <hr /> <Son textVal={this.state.fatherVal} changeVal={this.changeVal.bind(this)}></Son> </div> ) } } class Son extends Component { state = { sonVal:this.props.textVal, } render() { return ( <div> <h2>子組件:{this.state.sonVal}</h2> <input type="text" value={this.props.textVal} onChange={this.props.changeVal.bind(this)} /> </div> ) } }
getSnapshotBeforeUpdate函數(shù)
這是一個(gè)新增的更新階段的生命周期函數(shù),其在render后觸發(fā),其接收兩個(gè)參數(shù),還有一個(gè)返回值
參數(shù)1:更新之前的props
參數(shù)2:更新之前的state
返回值:該返回值會(huì)作為componentDidUpdate生命周期函數(shù)的第三個(gè)參數(shù)傳入,一般我們會(huì)返回更新之前的一些頁(yè)面狀態(tài),從而進(jìn)行頁(yè)面的狀態(tài)保持
注意:新舊生命周期函數(shù)不能同時(shí)使用,不然會(huì)報(bào)錯(cuò)
舉例:
現(xiàn)在有一個(gè)移動(dòng)端頁(yè)面,我現(xiàn)在希望當(dāng)組件更新之后,調(diào)整當(dāng)前滾動(dòng)的位置
import React, { Component } from 'react'
import './snapshot.css'
export default class SnapShot extends Component {
state = {
num:1
}
//當(dāng)組件加載時(shí)設(shè)置一個(gè)定時(shí)器,4秒之后修改狀態(tài)從而能夠觸發(fā)組件更新
componentDidMount(){
this.clearTime = setTimeout(() => {
this.setState({
num:2
})
},4000)
console.log(this.scrollSize)
}
//一般情況下,我們都會(huì)把組件內(nèi)設(shè)置的定時(shí)器,在組件卸載時(shí)清除掉
componentWillUnmount(){
clearTimeout(this.clearTime)
}
render() {
return (
//通過(guò)ref獲取到滾動(dòng)容器的DOM
<div className='wrap' ref={ref => this.wrap = ref}>
<div className="box1">1111</div>
<div className="box2">{this.state.num}</div>
</div>
)
}
//當(dāng)開始更新時(shí),將更新之前的wrap元素的scrollTop返回出去
getSnapshotBeforeUpdate(prevProps,prevState){
return this.wrap.scrollTop
}
//在更新之后,通過(guò)componentDidUpdate的第三個(gè)參數(shù)調(diào)出wrap元素更新之前的scrollTop的值減去300,再賦值給更新之后的wrap
componentDidUpdate(prevProps,prevState,prevScrollTop){
this.wrap.scrollTop = prevScrollTop - 300
}
}
SnapShot.css
*{
padding:0;
margin:0;
}
.wrap{
width:100vw;
height:100vh;
overflow: auto;
}
.box1{
height:1000px;
background:#f00;
}
.box2{
height:1000px;
background:#0f0;
}
代碼分析:
上面的例子中,我們的SnapShot組件應(yīng)該會(huì)在加載好4秒之后自動(dòng)修改內(nèi)部狀態(tài),從而觸發(fā)更新周期,然后在當(dāng)前滾動(dòng)的位置向上自動(dòng)滾動(dòng) 300px 的位置,這里主要依靠的就是
getSnapshotBeforeUpdate
將更新之前的scrollTop的值返回出去,然后更新之后的生命周期函數(shù)componentDidUpdate
可以調(diào)用到,并將更新之前的滾動(dòng)位置信息減去300再賦值給更新之后的scrollTop
特殊生命周期函數(shù) – React異常捕獲
從React16開始引入了一個(gè)新的概念叫做錯(cuò)誤邊界
錯(cuò)誤邊界可以理解成是一個(gè)React組件,其應(yīng)用場(chǎng)景與概念比較類似于我們之前在vue中講過(guò)的異步組件,只不過(guò)它并不是一個(gè)Promise對(duì)象,這種組件可以捕獲并打印發(fā)生在其子組件樹任何位置的原生JavaScript錯(cuò)誤,并且渲染出錯(cuò)誤情況下的UI界面,而不是渲染錯(cuò)誤的子組件樹
目前只有類組件可以成為錯(cuò)誤邊界組件,要實(shí)現(xiàn)錯(cuò)誤邊界組件需要使用以下兩個(gè)函數(shù)
getDerivedStateFromError(error)
componentDidCatch(error,info)
以上兩個(gè)函數(shù)任意一個(gè)或者兩個(gè)都用都可以構(gòu)成一個(gè)錯(cuò)誤邊界組件,兩個(gè)函數(shù)的功能類似
getDerivedStateFromError(error)
該方法是一個(gè)靜態(tài)方法,接收一個(gè)參數(shù),并返回一個(gè)對(duì)象
參數(shù):錯(cuò)誤信息
返回值:返回一個(gè)匿名對(duì)象,這個(gè)對(duì)象直接指向當(dāng)前組件的state
舉例:
import React, { Component } from 'react'
export default class ErrorComp extends Component {
constructor(props){
super(props)
this.state = {
error:false //error用來(lái)表示當(dāng)前錯(cuò)誤邊界組件是否有捕獲到錯(cuò)誤
}
}
static getDerivedStateFromError(error){
console.log(error) //打印錯(cuò)誤
return {
error:true //這里error是ErrorCpmp組件的內(nèi)部狀態(tài)error
}
}
render() {
if(this.state.error){
return (
<p>我是錯(cuò)誤位置:{this.state.text}</p>
)
}else{
return (
<Child></Child>
)
}
}
}
class Child extends Component {
render(){
throw new Error("我是一個(gè)錯(cuò)誤") //在子組件中認(rèn)為拋出一個(gè)錯(cuò)誤來(lái)觸發(fā)getDerivedStateFromError
return (
<div>渲染Child組件</div>
)
}
}
代碼分析:
上面的代碼中,我們創(chuàng)建了一個(gè)ErrorComp組件作為錯(cuò)誤邊界組件,在內(nèi)部調(diào)用 getDerivedStateFromError ,同時(shí)創(chuàng)建一個(gè)子組件Child并在其渲染的時(shí)候手動(dòng)拋出一個(gè)錯(cuò)誤,當(dāng)組件渲染時(shí),ErrorComp接收到子組件Child拋出的錯(cuò)誤觸發(fā) getDerivedStateFromError 通過(guò)該方法的返回值修改error狀態(tài)的值為true,在ErrorComp組件render時(shí),根據(jù)error狀態(tài)的值做一個(gè)判斷來(lái)決定渲染哪個(gè)子組件
componentDidCatch(error,info)
該方法的作用其實(shí)與上面的方法基本上一致,就是執(zhí)行邏輯不太一樣,接收兩個(gè)參數(shù),沒(méi)有返回值
參數(shù)1:錯(cuò)誤信息
參數(shù)2:自動(dòng)注入一個(gè)對(duì)象,對(duì)象內(nèi)部有一個(gè)componentStack屬性記錄當(dāng)前錯(cuò)誤的路徑
舉例:
import React, { Component } from 'react'
export default class ErrorComp extends Component {
constructor(props){
super(props)
this.state = {
error:false,
text:''
}
}
componentDidCatch(error,info){
console.log(error,info)
this.setState({ //通過(guò)setState修改內(nèi)部狀態(tài)(與上面的return其實(shí)是一樣的)
error:error,
text:info.componentStack //把錯(cuò)誤信息路徑賦值給text
})
}
render() {
return (
<div>
{
//即然是判斷,我們也可以采用三元運(yùn)算完成
this.state.error ? <p>{this.state.text}</p> : <Child></Child>
}
</div>
)
}
}
class Child extends Component {
render(){
throw new Error("我是一個(gè)錯(cuò)誤")
return (
<div>渲染Child組件</div>
)
}
}
代碼分析:
上面的例子其實(shí)執(zhí)行的效果與getDerivedStateFromError方法的執(zhí)行效果一樣,只不過(guò)可以多接收一個(gè)參數(shù)記錄錯(cuò)誤位置,一般我們可以把這個(gè)錯(cuò)誤信息作為錯(cuò)誤日志上傳給服務(wù)器,同時(shí)因?yàn)闆](méi)有return的方式修改錯(cuò)誤狀態(tài)error的值,所以我們直接使用setState來(lái)修改
以上兩個(gè)方法如果要一起用的話,我們可以用 getDerivedStateFromError 來(lái)修改錯(cuò)誤狀態(tài)值,用componentDidCatch 來(lái)調(diào)用一個(gè)上傳接口上傳把錯(cuò)誤信息作為上傳錯(cuò)誤日志
注意事項(xiàng):
- 錯(cuò)誤邊界組件只能捕獲自己的子組件中的錯(cuò)誤,所以我們一般會(huì)專門制作一個(gè)需要在某個(gè)子組件錯(cuò)誤時(shí)進(jìn)行渲染的備用組件,類似與在vue中如果異步組件的Promise返回的是一個(gè)錯(cuò)誤狀態(tài)就顯示注冊(cè)到錯(cuò)誤狀態(tài)下的組件
- 在開發(fā)環(huán)境下就算渲染出來(lái)了備用組件,依然還是會(huì)彈出React自己的報(bào)錯(cuò)頁(yè)面,生產(chǎn)環(huán)境下不會(huì),所以說(shuō)這些錯(cuò)誤邊界組件主要是為生產(chǎn)環(huán)境使用的
無(wú)法捕獲異常的情況
下面這些情況下錯(cuò)誤邊界無(wú)法捕獲到異常:
- 事件處理
- 異步代碼
- 服務(wù)端渲染
- 自身拋出來(lái)的錯(cuò)誤
對(duì)于錯(cuò)誤邊界無(wú)法捕獲的異常,可以使用js
的try...catch...
語(yǔ)法
舉例:我們?cè)诋?dāng)前組件自身內(nèi)部通過(guò)點(diǎn)擊事件觸發(fā)一個(gè)錯(cuò)誤拋出來(lái)實(shí)現(xiàn)上面兩個(gè)異常捕獲方法所實(shí)現(xiàn)的功能
import React, { Component } from 'react'
export default class ErrorComp extends Component {
constructor(props){
super(props)
this.state = {
error:null
}
}
handleClick() {
try {
throw new Error("我是一個(gè)錯(cuò)誤") //在try中手動(dòng)制作一個(gè)錯(cuò)誤拋出讓catch被執(zhí)行
} catch (error) {
this.setState({ error });
}
}
render() {
return (
<div>
{
this.state.error ? <p>{'我是錯(cuò)誤的'}</p> : <p>{'我是正確的'}</p>
}
<button onClick={this.handleClick.bind(this)}>按鈕</button>
</div>
)
}
}
代碼分析:
我們通過(guò)點(diǎn)擊事件觸發(fā)一個(gè)try…catch語(yǔ)句的執(zhí)行,并且在try中故意拋出一個(gè)錯(cuò)誤,從而修改error的狀態(tài)值,來(lái)控制當(dāng)前組件自己的渲染結(jié)果
總結(jié):
老生命周期函數(shù):
掛載階段:
-
constructor()
初始化整個(gè)組件的state 以及調(diào)用super(props),該方法只會(huì)執(zhí)行一次
-
componentWillMount()
掛載之前(已經(jīng)棄用,目前React18.2中依然還是可以使用,如果使用會(huì)報(bào)警告)
-
render()
只返回需要渲染頁(yè)面結(jié)構(gòu),最好不要包含其它的業(yè)務(wù)邏輯
-
componentDidMount()
掛載之后,可以獲取到DOM節(jié)點(diǎn)并操作,服務(wù)器請(qǐng)求,啟用事件監(jiān)聽(tīng),調(diào)用 setState()等都可以在此函數(shù)中
更新階段情況一:state更新時(shí)
-
shouldComponentUpdate(nextProps,nextState)
有兩個(gè)參數(shù)
nextProps
和nextState
,表示新的屬性和變化之后的state
,返回一個(gè)布爾值,true
表示會(huì)觸發(fā)重新渲染,false
表示不會(huì)觸發(fā)重新渲染,默認(rèn)返回true
,我們通常利用此生命周期來(lái)優(yōu)化React程序性能 -
componentWillUpdate()
準(zhǔn)備開始更新(已經(jīng)棄用,目前React18.2中依然還是可以使用,如果使用會(huì)報(bào)警告)
-
render()
重新渲染頁(yè)面結(jié)構(gòu)(與掛載階段共享同一個(gè)函數(shù))
-
componentDidUpdate()
在組件完成更新后立即調(diào)用,在初始化時(shí)不會(huì)被調(diào)用
更新階段情況二:props更新時(shí)
-
componentWillReceiveProps(nextProps)
外部傳入的數(shù)據(jù)發(fā)生變化時(shí)觸發(fā)(父組件修改了一個(gè)自己組件內(nèi)部的數(shù)據(jù),而這個(gè)被修改的數(shù)據(jù)時(shí)有傳遞給子組件的)
-
shouldComponentUpdate(nextProps,nextState)
有兩個(gè)參數(shù)
nextProps
和nextState
,表示新的屬性和變化之后的state
,返回一個(gè)布爾值,true
表示會(huì)觸發(fā)重新渲染,false
表示不會(huì)觸發(fā)重新渲染,默認(rèn)返回true
,我們通常利用此生命周期來(lái)優(yōu)化React程序性能 -
componentWillUpdate()
準(zhǔn)備開始更新(已經(jīng)棄用,目前React18.2中依然還是可以使用,如果使用會(huì)報(bào)警告)
-
render()
重新渲染頁(yè)面結(jié)構(gòu)(與掛載階段共享同一個(gè)函數(shù))
-
componentDidUpdate()
在組件完成更新后立即調(diào)用,在初始化時(shí)不會(huì)被調(diào)用
卸載階段:
1、componentWillUnmount()
當(dāng)組件卸載時(shí)觸發(fā),我們一般在這個(gè)函數(shù)里去清除一些定時(shí)器,取消網(wǎng)絡(luò)請(qǐng)求,清理無(wú)效的DOM元素等垃圾清理工作。
以上已被棄用的聲明周期函數(shù)
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
但是在當(dāng)前React18的版本中依然還是可以使用,但是回報(bào)警告,要求加上前綴UNSAFE_
新生命周期函數(shù)
掛載階段:
-
constructor()
初始化整個(gè)組件的state 以及調(diào)用super(props),該方法只會(huì)執(zhí)行一次
-
getDerivedStateFromProps(nextProps,prevState)
這是個(gè)靜態(tài)方法,使用時(shí)記得添加static修飾符,在初始掛載及后續(xù)更新時(shí)都會(huì)被調(diào)用,接收兩個(gè)參數(shù)分別是新傳入的props和修改之后的state,它返回一個(gè)對(duì)象來(lái)更新 state,如果返回 null 則不更新任何內(nèi)容。
-
render
只返回需要渲染頁(yè)面結(jié)構(gòu),最好不要包含其它的業(yè)務(wù)邏輯
-
componentDidMount()
組件裝載之后調(diào)用,可以獲取到DOM節(jié)點(diǎn)并操作,服務(wù)器請(qǐng)求,啟用事件監(jiān)聽(tīng),調(diào)用 setState()等都可以在此函數(shù)中
更新階段
-
getDerivedStateFromProps()
此方法在更新和掛載階段都會(huì)調(diào)用
-
shouldComponentUpdate(nextProps,nextState)
該方法有兩個(gè)參數(shù)nextProps和nextState,表示新的屬性和變化之后的state,返回一個(gè)布爾值,true表示會(huì)觸發(fā)重新渲染,false表示不會(huì)觸發(fā)重新渲染,默認(rèn)返回true,我們通常利用此生命周期來(lái)優(yōu)化React程序性能
-
render()
重新渲染頁(yè)面結(jié)構(gòu)(與掛載階段共享同一個(gè)函數(shù))
-
getSnapshotBeforeUpdate(prevProps,prevState)
這個(gè)方法在render之后,componentDidUpdate之前調(diào)用,有兩個(gè)參數(shù)prevProps和prevState,表示之前的屬性和之前的state,這個(gè)函數(shù)有一個(gè)返回值,會(huì)作為第三個(gè)參數(shù)傳給componentDidUpdate,如果你不想要返回值,可以返回null,此生命周期需要與componentDidUpdate搭配使用
-
componentDidUpdate(prevProps,prevState,snapshot)
該方法在getSnapshotBeforeUpdate方法之后被調(diào)用,有三個(gè)參數(shù)prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三個(gè)參數(shù)是getSnapshotBeforeUpdate返回的,如果觸發(fā)某些回調(diào)函數(shù)時(shí)需要用到 DOM 元素的狀態(tài),則將對(duì)比或計(jì)算的過(guò)程遷移至getSnapshotBeforeUpdate,然后在 componentDidUpdate中統(tǒng)一觸發(fā)回調(diào)或更新?tīng)顟B(tài)。
卸載階段
-
componentWillUnmount()文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-833280.html
當(dāng)組件被卸載或者銷毀了就會(huì)調(diào)用,我們可以在這個(gè)函數(shù)里去清除一些定時(shí)器,取消網(wǎng)絡(luò)請(qǐng)求,清理無(wú)效的DOM元素等垃圾清理工作。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-833280.html
到了這里,關(guān)于【學(xué)習(xí)前端第七十四課】React生命周期的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!