React新生命周期--getDerivedStateFromProps

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

新的生命周期過程

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


image.png

看看它的變化

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

getDerivedStateFromProps

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

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

由于16.4的修改,這個函數(shù)會在每次re-rendering之前被調用,這意味著什么呢?

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

使用

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

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

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

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

Case1 -- 多來源的不同狀態(tài)

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

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

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

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

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

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

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

如上面代碼的例子所示,組件既受控,又控制自己。當type發(fā)生變化,會觸發(fā)一次getDerivedStateFromProps,在這里更新組件的type狀態(tài),然而,在進行異步操作之后,組件又會更新list狀態(tài),這時你的getDerivedStateFromProps函數(shù)就需要注意,不能夠僅僅判斷type是否變化來更新狀態(tài),因為list的變化也會更新到組件的狀態(tài)。這時就必須返回一個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;
}

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

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

Case3 -- 異步

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

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

這樣的寫法已經(jīng)使用了很久,并且并不會存在什么功能上的問題,但是將componentWillReceiveProps標記為deprecated的原因也并不是因為功能問題,而是性能問題。

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

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.

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

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

那么如何使用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中根據(jù)新的props和state來進行異步操作,比如從服務端拉取數(shù)據(jù)。

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

小結

以上是本期開發(fā)過程中使用新的生命周期函數(shù)的時候遇到的一點小問題和一些相關思考。react為了防止部分開發(fā)者濫用生命周期,可謂非常盡心盡力了。既然你用不好,我就干脆不讓你用。一個靜態(tài)的生命周期函數(shù)可以讓狀態(tài)的修改更加規(guī)范和合理。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容