為什么要升級(jí)你的代碼?
這一次生命周期的升級(jí)非常重要,由于
react
官方即將發(fā)布的異步渲染機(jī)制,也就是React Fiber是什么.造成原來只會(huì)調(diào)用一次的生命周期會(huì)有可能多次調(diào)用谱邪。甚至是調(diào)用到一半然后作廢重新調(diào)用盲镶。所以在原來的reader
之前的生命周期都不在安全。如果你在render
之前的生命周期中進(jìn)行副作用的操作,如異步請(qǐng)求接口馅而,訂閱,耗時(shí)操作等其他的譬圣,都有可能出現(xiàn)意想不到的bug
瓮恭。所以我們要把這些原來的操作放到render
完成之后的生命周期,這樣可以保證我們進(jìn)行的副作用只會(huì)調(diào)用一次厘熟,下面我們來說一下之前我們的一些操作怎么升級(jí)到新的生命周期里屯蹦。那么現(xiàn)在開始吧。
關(guān)于 componentWillMount
的
初始化操作
有的人可能喜歡在componentWillMount
進(jìn)行初始化操作绳姨。例如:
class ExampleComponent extends React.Component {
state = {};
componentWillMount() {
this.setState({
currentColor: this.props.defaultColor,
palette: 'rgb',
});
}
}
這種操作最簡(jiǎn)單的方式就是把它放到構(gòu)造函數(shù)里或者屬性初始值設(shè)定項(xiàng)中:
// After
class ExampleComponent extends React.Component {
state = {
currentColor: this.props.defaultColor,
palette: 'rgb',
};
}
//或者:
class ExampleComponent extends React.Component {
constructor(props) {
super(props);
currentColor: props.defaultColor,
palette: 'rgb',
};
}
獲取外部數(shù)據(jù)--如異步調(diào)用的
有在componentWillMount
生命周期中進(jìn)行異步調(diào)用獲取數(shù)據(jù)的登澜,例如:
// Before
class ExampleComponent extends React.Component {
state = {
externalData: null,
};
componentWillMount() {
this._asyncRequest = loadMyAsyncData().then(
externalData => {
this._asyncRequest = null;
this.setState({externalData});
}
);
}
componentWillUnmount() {
if (this._asyncRequest) {
this._asyncRequest.cancel();
}
}
render() {
if (this.state.externalData === null) {
// Render loading state ...
} else {
// Render real UI ...
}
}
}
在即將到來的異步渲染模式里,有可能會(huì)多次調(diào)用這個(gè)生命周期飘庄,如果你在這里進(jìn)行接口請(qǐng)求脑蠕,就有可能多次調(diào)用接口。所以這里的建議是把請(qǐng)求接口的操作放到渲染完畢以后的componentDidMount
里跪削。
// After
class ExampleComponent extends React.Component {
state = {
externalData: null,
};
componentDidMount() {
this._asyncRequest = loadMyAsyncData().then(
externalData => {
this._asyncRequest = null;
this.setState({externalData});
}
);
}
componentWillUnmount() {
if (this._asyncRequest) {
this._asyncRequest.cancel();
}
}
render() {
if (this.state.externalData === null) {
// Render loading state ...
} else {
// Render real UI ...
}
}
}
很多人以為componentWillMount
和componentWillUnmount
是配對(duì)的谴仙。但是其實(shí)并不是。反而是componentDidMount
和componentWillUnmount
是一對(duì)碾盐。他們特別適合在訂閱/取消的操作
。例如:setTimeout廓旬、setInterval
;上面例子中請(qǐng)求接口/取消接口
等哼审。我們這里舉一個(gè)setInterval
的例子:
class Clock extends React.Component {
constructor(props){
super(props)
this.state = getClockTime();
}
componentDidMount(){
this.ticking = setInterval(()=>{
this.setState(getClockTime();
},1000);
}
componentWillUnmount(){
clearInterval(this.ticking);
}
render(){
...
}
}
關(guān)于componentWillReceiveProps
的升級(jí)
這個(gè)方法的升級(jí)是重中之重。在以前我們通過該方法來實(shí)現(xiàn)更新state
孕豹。以達(dá)到受控組件的目的,父組件可以控制子組件的更新十气,獲取數(shù)據(jù)励背,重新渲染等的操作。在使用新的getDerivedStateFromProps
方法來代替原來的componentWillReceiveProps
方法時(shí)砸西,原來的一些寫法也不在適用叶眉。
當(dāng)新的props
到來時(shí)更新state
下面是使用componentWillReceiverProps
來實(shí)現(xiàn)基于新的props
來更新state
.
class ExampleComponent extends React.Component {
state = {
isScrollingDown: false,
};
componentWillReceiveProps(nextProps) {
if (this.props.currentRow !== nextProps.currentRow) {
this.setState({
isScrollingDown:
nextProps.currentRow > this.props.currentRow,
});
}
}
}
雖然上面的代碼并沒有問題,但是componentWillReceiveProps
經(jīng)常被錯(cuò)誤的使用芹枷。因此該方法將被廢棄!
從版本16.3
開始衅疙,我們推薦使用stattic getDerivedStateFromProps(props,state)
來實(shí)現(xiàn)基于props
對(duì)state
的更新。即將廢棄的三個(gè)方法的功能都會(huì)被該方法替代鸳慈。也即是說饱溢,組件在掛載時(shí),更新props
時(shí)走芋,更新state
時(shí)绩郎,forceUpdate
時(shí)潘鲫,都會(huì)調(diào)用該方法。
換一句話說肋杖。只要你render
就會(huì)調(diào)用該方法溉仑。由于該方法是一個(gè)靜態(tài)方法,無法拿到該組件的實(shí)例状植。
因?yàn)槠浔活l繁的調(diào)用浊竟。同樣在這個(gè)方法里也不適合進(jìn)行耗時(shí)的操作。
我們看一下官方給出的例子:
class ExampleComponent extends React.Component {
state = {
isScrollingDown:false,
lastRow:null;
}
static getDerivedStateFromProps(props,state){
if(props.currentRow 津畸!== state.lastRow){
return {
isScrollingDown:props.currentRow > state.lastRow,
lastRow:props.currentRow,
}
}
return null;
}
}
在上面的例子中:static getDerivedStateFromProps(props,state)
由于是靜態(tài)方法振定,因?yàn)槟貌坏浇M件的實(shí)例。所以在這里也就不能通過this.props.currentRow
來拿到當(dāng)前實(shí)例對(duì)象中保存的currentRow
數(shù)據(jù)洼畅。
在上面的例子中是通過把鏡像數(shù)據(jù)保存到state
里吩案,然后在新的props
到來時(shí)進(jìn)行比較來達(dá)到同樣的效果的。
當(dāng)新的props
到來時(shí)帝簇,進(jìn)行異步接口請(qǐng)求.
我們有時(shí)候也會(huì)有這種需求徘郭。當(dāng)新的props
更新時(shí),我們需要重新獲取數(shù)據(jù)來更新頁面丧肴,例如:
class ExampleComponent extends React.Component {
state = {
externalData:null,
}
componentDidMount(){
this._loadAsyncData(this.props.id);
}
componentWillReceiveProps(nextProps){
if(nextProps.id !== this.props.id){
this.setState({externalData:null});
this._loadAsyncData(nextProps.id)
}
}
componentWillUnmount(){
if(this._asyncRequest){
this._asyncRequest.cancel();
}
}
render(){
if(this.state.externalData === null){
//...
}else {
//...
}
}
_laodAsyncData(id){
this._asyncRequest = loadMyAsyncData(id).then(
externalData => {
this._asyncRequest = null;
this.setState({externalData});
}
);
}
}
在上面的例子中残揉,我們獲取數(shù)據(jù)的依據(jù)是props
的id
。所以我們需要在componentWillReceiveProps
中監(jiān)聽id
的變化芋浮。當(dāng)變化發(fā)生時(shí)觸發(fā)異步獲取數(shù)據(jù)抱环。
而這里的代碼升級(jí)可以說秉承著一個(gè)原則,就是凡是異步獲取纸巷,或者有副作用的操作镇草,通通扔到生命周期的后面去,也就是render
完以后瘤旨,以保證數(shù)據(jù)只執(zhí)行一次梯啤。
在這里就是把數(shù)據(jù)獲取放到componentDidUpdate
中。而我們的getDerivedStateFromProps
生命周期干嘛呢存哲?
更新props.id
的數(shù)據(jù)到state
里因宇。置空externalData
為null
。我們通過判斷externalData
來獲取數(shù)據(jù)祟偷。
class ExampleComponent extends React.Component {
state = {
externalData:null,
}
static getDerivedStateFromProps(props,state){
//我們把props.id的值保存為state.prevID察滑。當(dāng)新的props.id到來時(shí),我們用來做比較
// 清除原來的數(shù)據(jù)修肠,這樣我們可以基于此來判斷是否需要重新獲取數(shù)據(jù)
if(props.id 1== state.prevID){
return {
externalData:null,
prevID:props.id
}
}
return null;
}
componentDidMount(){
this._loadAsyncData(this.props.id);
}
//在更新完成以后贺辰,通過externalData是否為空來判斷是否需要更新數(shù)據(jù)
componentDidUpdate(prevProps,prevState){
if(this.state.externalData === null){
this._loadAsyncData(this.props.id);
}
}
componentWillUnmount(){
if(this._asyncRequest){
this._asyncRequest.cancel();
}
}
render(){
if(this.state.externalData === null){
//...
}else {
//...
}
}
_laodAsyncData(id){
this._asyncRequest = loadMyAsyncData(id).then(
externalData => {
this._asyncRequest = null;
this.setState({externalData});
}
);
}
}
現(xiàn)在我們總結(jié)一下getDerivedStateFromProps
和componentWillReceiveProps
使用上的差異:
角標(biāo) | componentWillReceiveProps |
getDerivedStateFromProps |
---|---|---|
如何確定是否更新state
|
if(nextProps.id !== this.props.id){} |
把鏡像保存到state 里。通過保存在state 里的鏡像來進(jìn)行比較判斷if(props.id 1== state.prevID){
|
如何確定是否需要重新獲取數(shù)據(jù) | 通過 if(nextProps.id !== this.props.id){} 直接就可以知道需要重新獲取數(shù)據(jù) |
通過保存在state 里的鏡像來進(jìn)行比較判斷,在上面的例子是通過把數(shù)據(jù)置空魂爪,我們也可以設(shè)置一個(gè)loading 為true 先舷。然后在componentDidUpdate(prevProps,prevState){} 判斷來觸發(fā)獲取數(shù)據(jù)的操作。 |
想比較于原來的做法確實(shí)要繁瑣一點(diǎn)滓侍。
關(guān)于componentWillUpdate
關(guān)于這個(gè)的生命周期蒋川,我在實(shí)際開發(fā)中基本沒有用到過。官網(wǎng)例子中舉了一個(gè)render
之前獲取DOM
屬性的操作撩笆。
然后使用新的生命周期getSnapshotBeforeUpdate
來取代一下:
class ScrollingList extends React.Component {
listRef = null;
getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the scroll position so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
return (
this.listRef.scrollHeight - this.listRef.scrollTop
);
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
if (snapshot !== null) {
this.listRef.scrollTop =
this.listRef.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.setListRef}>
{/* ...contents... */}
</div>
);
}
setListRef = ref => {
this.listRef = ref;
};
}
通過getSnapshotBeforeUpdate
拿到數(shù)據(jù)以后捺球,把數(shù)據(jù)返回,然后在componentDidUpdate(prevProps, prevState, snapshot)
的第三個(gè)參數(shù)拿到我們之前返回的數(shù)據(jù)夕冲,然后進(jìn)行一些操作氮兵。
到這里就寫完了。新的生命周期getDerivedStateFromProps
我把它放在了關(guān)于componentWillReceiveProps的升級(jí)
章節(jié)歹鱼。
over....
參考學(xué)習(xí)的文章: