聊聊React v16.3的UNSAFE類生命週期

不知道小夥伴有沒有注意到,自從react更新到16.3版本後,之前使用的 componentWillMountcomponentWillReceivePropscomponentWillUpdate三個生命週期函數都有eslint報警,讓咱們使用 UNSAFE_前綴的新的生命週期函數。不由有疑問「react這是意欲何爲啊」?爲何要加 UNSAFE_前綴?

爲何要對這些生命週期函數報警?

一、componentWillMountreact

componentWillMount生命週期發生在首次渲染前,通常使用的小夥伴大多在這裏初始化數據或異步獲取外部數據賦值。初始化數據,react官方建議放在constructor裏面。而異步獲取外部數據,渲染並不會等待數據返回後再去渲染。瀏覽器

案例一:以下是安裝時監聽外部事件調度程序的組件示例安全

class Example extends React.Component {   
    state = {
        value: ''
    };
    componentWillMount() {    
        this.setState({       
            value: this.props.source.value
        });       
        this.props.source.subscribe(this.handleChange);
    }   
    componentWillUnmount() {    
        this.props.source.unsubscribe(this.handleChange ); 
    }   
    handleChange = source => {    
        this.setState({
            value: source.value
        });   
    }; 
}
複製代碼

試想一下,假如組件在第一次渲染的時候被中斷,因爲組件沒有完成渲染,因此並不會執行componentWillUnmount生命週期(注:不少人常常認爲componentWillMount和componentWillUnmount老是配對,但這並非必定的。只有調用componentDidMount後,React才能保證稍後調用componentWillUnmount進行清理)。所以handleSubscriptionChange仍是會在數據返回成功後被執行,這時候setState因爲組件已經被移除,就會致使內存泄漏。因此建議把異步獲取外部數據寫在componentDidMount生命週期裏,這樣就能保證componentWillUnmount生命週期會在組件移除的時候被執行,避免內存泄漏的風險。bash

如今,小夥伴清楚爲何了要用UNSAFE_componentWillMount替換componentWillMount了吧(注意:這裏的UNSAFE並非指安全性,而是表示使用這些生命週期的代碼將更有可能在將來的React版本中存在缺陷,特別是一旦啓用了異步渲染異步

二、componentWillReceivePropsasync

componentWillReceiveProps生命週期是在props更新時觸發。通常用於props參數更新時同步更新state參數。但若是在componentWillReceiveProps生命週期直接調用父組件的某些有調用setState的函數,會致使程序死循環。函數

案例二:以下是子組件componentWillReceiveProps裏調用父組件改變state的函數示例ui

...
class Parent extends React.Component{
    constructor(){
        super();
        this.state={
            list: [],
            selectedData: {}
        };
    }
    
    changeSelectData = selectedData => {
        this.setState({
            selectedData
        });
    }
    
    render(){
        return (
            <Clild list={this.state.list} changeSelectData={this.changeSelectData}/>
        );
    }
}

...
class Child extends React.Component{
    constructor(){
        super();
        this.state={
            list: []
        };
    }
    componentWillReceiveProps(nextProps){
        this.setState({
            list: nextProps.list
        })
        nextProps.changeSelectData(nextProps.list[0]); //默認選擇第一個
    }
    ...
}
複製代碼

如上代碼,在Child組件的componentWillReceiveProps裏直接調用Parent組件的changeSelectData去更新Parent組件stateselectedData值。會觸發Parent組件從新渲染,而Parent組件從新渲染會觸發Child組件的componentWillReceiveProps生命週期函數執行。如此就會陷入死循環。致使程序崩潰。this

因此,React官方把componentWillReceiveProps替換爲UNSAFE_componentWillReceiveProps,讓小夥伴在使用這個生命週期的時候注意它會有缺陷,要注意避免,好比上面例子,ChildcomponentWillReceiveProps調用changeSelectData時先判斷list是否有更新再肯定是否要調用,就能夠避免死循環。spa

三、componentWillUpdate

componentWillUpdate生命週期在視圖更新前觸發。通常用於視圖更新前保存一些數據方便視圖更新完成後賦值。 案例三:以下是列表加載更新後回到當前滾動條位置的案例

class ScrollingList extends React.Component {   
    listRef = null;   
    previousScrollOffset = null;   
    componentWillUpdate(nextProps, nextState) {    
        if (this.props.list.length < nextProps.list.length) {      
            this.previousScrollOffset = this.listRef.scrollHeight - this.listRef.scrollTop;    
        } 
    }   
    componentDidUpdate(prevProps, prevState) {    
        if (this.previousScrollOffset !== null) {      
            this.listRef.scrollTop = this.listRef.scrollHeight - this.previousScrollOffset;  
            this.previousScrollOffset = null;    
        }   
    }   
    render() {    
        return (       
            `<div>` {/* ...contents... */}`</div>`     
        );   
    }   
    setListRef = ref => {    this.listRef = ref;   };
}
複製代碼

因爲componentWillUpdatecomponentDidUpdate這兩個生命週期函數有必定的時間差(componentWillUpdate後通過渲染、計算、再更新DOM元素,最後才調用componentDidUpdate),若是這個時間段內用戶恰好拉伸了瀏覽器高度,那componentWillUpdate計算的previousScrollOffset就不許確了。若是在componentWillUpdate進行setState操做,會出現屢次調用只更新一次的問題,把setState放componentDidUpdate,能保證每次更新只調用一次。

因此,react官方建議把componentWillUpdate替換爲UNSAFE_componentWillUpdate。若是真的有以上案例的需求,可使用16.3新加入的一個周期函數getSnapshotBeforeUpdate。下面會有具體說明,這裏暫時賣個關子。

有什麼替換方案?

一、getDerivedStateFromProps

getDerivedStateFromProps是官方在16.3新加入的生命週期函數,props變化時被調用,如果父組件從新渲染,也會被調用。它返回新的props值。

案例四:以下是getDerivedStateFromProps的使用實例

class Example extends React.Component {   
    static getDerivedStateFromProps(nextProps, prevState) { 
        if(nextProps.name !== prevState.name) {
            return {
                name: nextProps.name
            }
        }
    } 
}
複製代碼

能夠看到,getDerivedStateFromProps接收最新的PropsnextProps、上一個stateprevState兩個參數,返回返回一個對象來更新state,或者返回null表示不須要更新state。要注意的是,getDerivedStateFromProps不能訪問this,因此若是要跟上一個props值做比較,只能是把上一個props值存到state裏做爲鏡像。到這裏你必定有疑問,爲何不把上一個props值傳給getDerivedStateFromProps?官方給的解析以下:

  • 在第一次調用getDerivedStateFromProps(實例化後)時,prevProps參數將爲null,須要在訪問prevProps時添加if-not-null檢查。

  • 沒有將之前的props傳遞給這個函數,在將來版本的React中釋放內存的一個步驟。 (若是React不須要將先前的道具傳遞給生命週期,那麼它不須要將先前的道具對象保留在內存中。)

綜上可知,getDerivedStateFromProps正是官方新加入的用以替代componentWillReceiveProps的方案。若是說,你的項目會考慮日後的版本兼容,建議改用getDerivedStateFromProps

二、getSnapshotBeforeUpdate

getSnapshotBeforeUpdate是跟getDerivedStateFromProps一塊兒,在16.3新加入的生命週期函數。觸發的時機在最近的更改被提交到DOM元素前,使得組件能夠在更改以前得到當前值,今生命週期返回的任意值都會做爲第三個參數傳給componentDidUpdate。通常當咱們須要在更新DOM前須要保存DOM當前的狀態時會使用這個生命週期,比較常見是用於DOM更新前獲取滾動位置,更新後恢復到該滾動位置。好比上面的案例三,componentWillUpdate更好的替換方案就是getSnapshotBeforeUpdategetSnapshotBeforeUpdatecomponentDidUpdate只通過了更新DOM這一操做。

案例五:以下爲案例三的更好的替換方案

class ScrollingList extends React.Component {   
    listRef = null;   
    getSnapshotBeforeUpdate(prevProps, prevState) {    
        if (prevProps.list.length < this.props.list.length) {      
            return this.listRef.scrollHeight - this.listRef.scrollTop;    
        } 
        return null;
    }   
    componentDidUpdate(prevProps, prevState, snapshot) {    
        if (snapshot !== null) {      
            this.listRef.scrollTop = this.listRef.scrollHeight - snapshot;   
        }   
    }   
    render() {    
        return (       
            `<div>` {/* ...contents... */}`</div>`     
        );   
    }   
    setListRef = ref => {    this.listRef = ref;   };
}
複製代碼

最後,關於componentWillMount的替換方案,官方建議把該生命週期函數的邏輯處理放到componentDidMount裏面去。

隨着React版本迭代,會否兼容UNSAFE類生命週期

React官網上的計劃是:

  • 16.3:爲不安全生命週期引入別名UNSAFE_componentWillMountUNSAFE_componentWillReceivePropsUNSAFE_componentWillUpdate。 (舊的生命週期名稱和新的別名均可以在此版本中使用。)

  • 將來的16.x版本:爲componentWillMountcomponentWillReceivePropscomponentWillUpdate啓用棄用警告。 (舊的生命週期名稱和新的別名均可以在此版本中使用,但舊名稱會記錄DEV模式警告。)

  • 17.0:刪除componentWillMountcomponentWillReceivePropscomponentWillUpdate。 (從如今開始,只有新的「UNSAFE_」生命週期名稱將起做用。)

結論

其實,說了這麼多。就兩點:

一、React意識到componentWillMountcomponentWillReceivePropscomponentWillUpdate這三個生命週期函數有缺陷,比較容易致使崩潰。可是因爲舊的項目已經在用以及有些老開發者習慣用這些生命週期函數,因而經過給它加UNSAFE_來提醒用它的人要注意它們的缺陷。

二、React加入了兩個新的生命週期函數getSnapshotBeforeUpdategetDerivedStateFromProps,目的爲了即便不使用這三個生命週期函數,也能實現只有這三個生命週期能實現的功能。

ps:本文部份內容借鑑參考文章ReactV16.3即將更改的生命週期

相關文章
相關標籤/搜索