react學習-react的生命週期和觸發順序

React 16是最近一年多React更新最大的版本。除了讓大家喜聞樂見的向下兼容的Fiber,防止了客戶端react在進行渲染的時候阻塞頁面的其他交互行爲。Fiber源碼速覽
參考https://juejin.im/post/5bea68a6e51d450cb20fdd70

新的生命週期過程

先來看看最新版本react的生命週期圖:

 

看看它的變化

新增:getDerivedStateFromProps,getSnapshotBeforeUpdate
UNSAFE:UNSAFE_componentWillMount,UNSAFE_componentWillUpdate,UNSAFE_componentWillReceiveProps

生命週期的觸發順序:

3.組件加載的時候觸發的函數:
順序:constructor -> componentWillMount -> render -> componentDidMount

4.組件數據更新的時候觸發的生命週期函數:
shouldComponentUpdate -> componentWillUpdate -> render - >componentDidUpdate

5.你在父組件裏面改變props傳值的時候觸發的:
componentWillReceiveProps   //已經被廢棄 ,使用getDerivedStateFromProps代替。

所以在你需要使用componentWillReceiveProps的時候,就可以考慮使用getDerivedStateFromProps來進行替代了。

兩者的參數是不相同的,而getDerivedStateFromProps是一個靜態函數,也就是這個函數不能通過this訪問到class的屬性,也並不推薦直接訪問屬性。而是應該通過參數提供的nextProps以及prevState來進行判斷,根據新傳入的props來映射到state。

需要注意的是,如果props傳入的內容不需要影響到你的state,那麼就需要返回一個null,這個返回值是必須的,所以儘量將其寫到函數的末尾。

6.組件銷燬的時候觸發的:
componentWillUnmount

getDerivedStateFromProps

React生命週期的命名一直都是非常語義化的,這個生命週期的意思就是從props中獲取state,可以說是太簡單易懂了。

可以說,這個生命週期的功能實際上就是將傳入的props映射到state上面

由於16.4的修改,這個函數會在每次re-rendering之前被調用,這意味着什麼呢?

意味着即使你的props沒有任何變化,而是父state發生了變化,導致子組件發生了re-render,這個生命週期函數依然會被調用。看似一個非常小的修改,卻可能會導致很多隱含的問題。

使用

這個生命週期函數是爲了替代componentWillReceiveProps存在的,所以在你需要使用componentWillReceiveProps的時候,就可以考慮使用getDerivedStateFromProps來進行替代了。

兩者的參數是不相同的,而getDerivedStateFromProps是一個靜態函數,也就是這個函數不能通過this訪問到class的屬性,也並不推薦直接訪問屬性。而是應該通過參數提供的nextProps以及prevState來進行判斷,根據新傳入的props來映射到state。

需要注意的是,如果props傳入的內容不需要影響到你的state,那麼就需要返回一個null,這個返回值是必須的,所以儘量將其寫到函數的末尾。

static getDerivedStateFromProps(nextProps, prevState) {
    const {type} = nextProps;
    // 當傳入的type發生變化的時候,更新state
    if (type !== prevState.type) {
        return {
            type,
        };
    }
    // 否則,對於state不進行任何操作
    return null;
}

Case1 -- 多來源的不同狀態

假設我們有一個列表,這個列表受到頁面主體,也就是根組件的驅動,也受到其本身數據加載的驅動。

因爲這個頁面在開始渲染的時候,所有的數據請求可能是通過batch進行的,所以要在根組件進行統一處理,而其列表的分頁操作,則是由其本身控制。

這會出現什麼問題呢?該列表的狀態受到兩方面的控制,也就是re-render可能由props驅動,也可能由state驅動。這就導致了getDerivedStateFromProps會在兩種驅動狀態下被重新渲染。

當這個函數被多次調用的時候,就需要注意到,state和props的變化將會怎樣影響到你的組件變化。

// 組件接收一個type參數
static propTypes = {
    type: PropTypes.number
}

// 組件還具有自己的狀態來渲染列表
class List extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            list: [],
            type: 0,
        }
    }
}

如上面代碼的例子所示,組件既受控,又控制自己。當type發生變化,會觸發一次getDerivedStateFromProps,在這裏更新組件的type狀態,然而,在進行異步操作之後,組件又會更新list狀態,這時你的getDerivedStateFromProps函數就需要注意,不能夠僅僅判斷type是否變化來更新狀態,因爲list的變化也會更新到組件的狀態。這時就必須返回一個null,否則會導致組件無法更新並且報錯。

Case2 -- 組織好你的組件

考慮一下,如果你的組件內部既需要修改自己的type,又需要接收從外部修改的type。

是不是非常混亂?getDerivedStateFromProps中你根本不知道該做什麼。

static getDerivedStateFromProps(nextProps, prevState) {
    const {type} = nextProps;
    // type可能由props驅動,也可能由state驅動,這樣判斷會導致state驅動的type被回滾
    if (type !== prevState.type) {
        return {
            type,
        };
    }
    // 否則,對於state不進行任何操作
    return null;
}

如何解決這個棘手的問題呢?

好好組織你的組件,在非必須的時候,摒棄這種寫法。type要麼由props驅動,要麼完全由state驅動。
如果實在沒有辦法解耦,那麼就需要一個hack來輔助:綁定props到state上。

constructor(props) {
    super(props);
    this.state = {
        type: 0,
        props,
    }
}
static getDerivedStateFromProps(nextProps, prevState) {
    const {type, props} = nextProps;
    // 這段代碼可能看起來非常混亂,這個props可以被當做緩存,僅用作判斷
    if (type !== props.type) {
        return {
            type,
            props: {
                type,
            },
        };
    }
    // 否則,對於state不進行任何操作
    return null;
}

上面的代碼可以保證在進行多數據源驅動的時候,狀態能夠正確改變。當然,這樣的代碼很多情況下是會影響到別人閱讀你的代碼的,對於維護造成了非常大的困難。

從這個生命週期的更新來看,react更希望將受控的propsstate進行分離,就如同Redux作者Dan Abramov在redux文檔當中寫的一樣Presentational and Container Components,將所有的組件分離稱爲展示型組件和容器型組件,一個只負責接收props來改變自己的樣式,一個負責保持其整個模塊的state。這樣可以讓代碼更加清晰。但是在實際的業務邏輯中,我們有時很難做到這一點,而且這樣可能會導致容器型組件變得非常龐大以致難以管理,如何進行取捨還是需要根據實際場景決定的。

Case3 -- 異步

以前,我們可以在props發生改變的時候,在componentWillReceiveProps中進行異步操作,將props的改變驅動到state的改變。

componentWillReceiveProps(nextProps) {
    if (props.type !== nextProps.type) {
        // 在這裏進行異步操作或者更新狀態
        this.setState({
            type: props.type,
        });
        this._doAsyncOperation();
    }
}

這樣的寫法已經使用了很久,並且並不會存在什麼功能上的問題,但是將componentWillReceiveProps標記爲deprecated的原因也並不是因爲功能問題,而是性能問題。

當外部多個屬性在很短的時間間隔之內多次變化,就會導致componentWillReceiveProps被多次調用。這個調用並不會被合併,如果這次內容都會觸發異步請求,那麼可能會導致多個異步請求阻塞。

getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.

這個生命週期函數會在每次調用render之前被觸發,而讀過一點react源碼的童鞋都會了解,reactsetState操作是會通過transaction進行合併的,由此導致的更新過程是batch的,而react中大部分的更新過程的觸發源都是setState,所以render觸發的頻率並不會非常頻繁(感謝 @leeenx20 的提醒,這裏描述進行了修改)。

在使用getDerivedStateFromProps的時候,遇到了上面說的props在很短的時間內多次變化,也只會觸發一次render,也就是隻觸發一次getDerivedStateFromProps。這樣的優點不言而喻。

那麼如何使用getDerivedStateFromProps進行異步的處理呢?

If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.

官方教你怎麼寫代碼系列,但是其實也沒有其他可以進行異步操作的地方了。爲了響應props的變化,就需要在componentDidUpdate中根據新的props和state來進行異步操作,比如從服務端拉取數據。

// 在getDerivedStateFromProps中進行state的改變
static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.type !== prevState.type) {
        return {
            type: nextProps.type,
        };
    }
    return null;
}
// 在componentDidUpdate中進行異步操作,驅動數據的變化
componentDidUpdate() {
    this._loadAsyncData({...this.state});
}

小結

react爲了防止部分開發者濫用生命週期,可謂非常盡心盡力了。既然你用不好,我就乾脆不讓你用。一個靜態的生命週期函數可以讓狀態的修改更加規範和合理。

至於爲什麼全文沒有提到getSnapshotBeforeUpdate,因爲自己並沒有用到#誠實臉。簡單看了一下,這個函數返回一個update之前的快照,並且傳入到componentDidUpdate中,組件更新前後的狀態都可以在componentDidUpdate中獲取了。一些需要在組件更新完成之後進行的操作所需要的數據,就可以不需要掛載到state或者是cache下來了。比如官方例子中所舉例的保留更新之前的頁面滾動距離,以便在組件update完成之後恢復其滾動位置。也是一個非常方便的週期函數。

轉載鏈接:https://www.jianshu.com/p/50fe3fb9f7c3

                           https://my.oschina.net/woddp/blog/3041551

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章