[React] componentWillReceiveProps引起的死循環(huán)

1. 背景

父組件異步獲取數(shù)據(jù),傳遞給子組件,子組件在這些數(shù)據(jù)中進行選擇。
當(dāng)選項發(fā)生改變的時候,父組件能根據(jù)選項做出響應(yīng)。

子組件要默認選中第一個。
而且,從開始裝載到默認選中第一個,也視為選項發(fā)生了改變。

2. 問題

2.1 父組件

它render了以下子組件,

<NumberSelector numbers={numbers} onChange={onNumberSelectorChange} />

其中,numbers來自父組件state,初始值為空數(shù)組[ ]
componentDidMount通過ajax異步取值后,修改state

onNumberSelectorChange的定義如下,

onNumberSelectorChange = number => this.setState({
    selectedNumber: number,
});

它會在子組件onChange事件發(fā)生后,修改父組件的state。

2.2 子組件

由于父組件componentDidMount中的ajax返回后,numbers才有值,
所以,子組件在裝載時,接受到的numbers為父組件的默認值[ ]。

父組件ajax返回后更新state,會重新render,
從而導(dǎo)致子組件更新。

因此,在子組件componentWillReceiveProps中,
才可以接到ajax返回后的numbers值。

componentWillReceiveProps = ({ numbers, onChange }) => {
    const {
        state: { selectedNumber },
    } = this;

    const firstNumber = numbers[0];
    this.setState({
        selectedNumber: firstNumber,
    });

    onChange(firstNumber);
};

2.3 結(jié)果

父組件的componentDidMount會拋異常:

> Uncaught (in promise) RangeError: Maximum call stack size exceeded

3. 原因分析

子組件的componentWillReceiveProps函數(shù)陷入了死循環(huán)。

在此函數(shù)中,子組件使用onChange(firstNumber);向父組件傳值,
父組件通過onNumberSelectorChange改變自身的state。

由于React在render時,
不管子組件屬性是否改變,都會調(diào)用子組件的componentWillReceiveProps。

componentWillReceiveProps :
"Note that React may call this method even if the props have not changed...

因此,父組件改變了自身state后,即使子組件的屬性沒有變化,
也會觸發(fā)componentWillReceiveProps。

因此,子組件在componentWillReceiveProps中,
調(diào)用onChange更改父組件的state,
會引發(fā)子組件的componentWillReceiveProps再次被調(diào)用,導(dǎo)致死循環(huán)。

最終調(diào)用棧溢出。

4. 解決方案

componentWillReceiveProps中可以更改父組件狀態(tài),
但是要增加判斷條件,避免陷入死循環(huán)。

// 設(shè)置默認選中第一項
componentWillReceiveProps = ({ numbers, onChange }) => {
    const {
        state: { selectedNumber },
    } = this;

    // 如果numbers清空了,且內(nèi)部有狀態(tài),就清空狀態(tài),觸發(fā)onChange
    if (numbers.length === 0 && selectedNumber != null) {
        this.setState({
            selectedNumber: null,
        });

        // 向父組件傳null值
        onChange(null);
        return;
    }

    // 注:終止條件 1
    // 如果numbers清空了,且內(nèi)部無狀態(tài),則不觸發(fā)onChange
    if (numbers.length === 0) {
        return;
    }

    // 注:終止條件 2
    // 如果selectedNumber在numbers中,就不改變它,直接返回
    const isContainedInNumbers = numbers.some(number => number === selectedNumber);
    if (isContainedInNumbers) {
        return;
    }

    // 否則,設(shè)置為選中第一項
    const firstNumber = numbers[0];
    this.setState({
        selectedNumber: firstNumber,
    });

    // 由于onClick會更新父組件state,導(dǎo)致父組件重新render,
    // 而React在render時,不管子組件屬性是否改變,都會調(diào)用componentWillReceiveProps,
    // 因此,onClick可能會導(dǎo)致componentWillReceiveProps死循環(huán)
    // 不過沒關(guān)系,我們前面加上了終止條件
    onChange(firstNumber);
};

以上代碼新增了isContainedInNumbers判斷,
從而可以在selectedNumber被設(shè)置后,
避免連續(xù)觸發(fā)componentWillReceiveProps。

參考

React Docs - componentWillReceiveProps()
Github: thzt/receive-props-infinite-loop

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原教程內(nèi)容詳見精益 React 學(xué)習(xí)指南,這只是我在學(xué)習(xí)過程中的一些閱讀筆記,個人覺得該教程講解深入淺出,比目前大...
    leonaxiong閱讀 2,944評論 1 18
  • 自己最近的項目是基于react的,于是讀了一遍react的文檔,做了一些記錄(除了REFERENCE部分還沒開始讀...
    潘逸飛閱讀 3,741評論 1 10
  • 目前,react組件有三種寫法,分別是es5的createClass寫法,es6的class寫法,以及statel...
    ZoomFunc閱讀 1,911評論 0 1
  • 最近看了一本關(guān)于學(xué)習(xí)方法論的書,強調(diào)了記筆記和堅持的重要性。這幾天也剛好在學(xué)習(xí)React,所以我打算每天堅持一篇R...
    gaoer1938閱讀 1,816評論 0 5
  • 一:一天二十四個小時,我們每天花多少時間在手機上,你計算過么?早上睡醒,有沒有習(xí)慣拿起手機,中午吃飯的時候會不會掏...
    咩的一聲羊叫閱讀 579評論 0 2

友情鏈接更多精彩內(nèi)容