React componentWillReceiveProps死循环问题

1. 背景

父组件异步获取数据,传递给子组件,子组件在这些数据中进行选择。
当选项发生改变的时候,父组件能根据选项做出响应。

子组件要默认选中第一个。
而且,从开始装载到默认选中第一个,也视为选项发生了改变。

2. 问题

2.1 父组件

它render了以下子组件,

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

其中,numbers来自父组件state,初始值为空数组[ ],
componentDidMount通过ajax异步取值后,修改state。

onNumberSelectorChange的定义如下,

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

它会在子组件onChange事件发生后,修改父组件的state。

2.2 子组件

由于父组件componentDidMount中的ajax返回后,numbers才有值,
所以,子组件在装载时,接受到的numbers为父组件的默认值[ ]。

父组件ajax返回后更新state,会重新render,
从而导致子组件更新。

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

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

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

    onChange(firstNumber);
};
2.3 结果

父组件的componentDidMount会抛异常:

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

3. 原因分析

子组件的componentWillReceiveProps函数陷入了死循环。

在此函数中,子组件使用onChange(firstNumber);向父组件传值,
父组件通过onNumberSelectorChange改变自身的state。

由于React在render时,
不管子组件属性是否改变,都会调用子组件的componentWillReceiveProps。

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

因此,父组件改变了自身state后,即使子组件的属性没有变化,
也会触发componentWillReceiveProps。

因此,子组件在componentWillReceiveProps中,
调用onChange更改父组件的state,
会引发子组件的componentWillReceiveProps再次被调用,导致死循环。

最终调用栈溢出。

4. 解决方案

componentWillReceiveProps中可以更改父组件状态,
但是要增加判断条件,避免陷入死循环。

// 设置默认选中第一项
componentWillReceiveProps = ({ numbers, onChange }) => {
    const {
        state: { selectedNumber },
    } = this;

    // 如果numbers清空了,且内部有状态,就清空状态,触发onChange
    if (numbers.length === 0 && selectedNumber != null) {
        this.setState({
            selectedNumber: null,
        });

        // 向父组件传null值
        onChange(null);
        return;
    }

    // 注:终止条件 1
    // 如果numbers清空了,且内部无状态,则不触发onChange
    if (numbers.length === 0) {
        return;
    }

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

    // 否则,设置为选中第一项
    const firstNumber = numbers[0];
    this.setState({
        selectedNumber: firstNumber,
    });

    // 由于onClick会更新父组件state,导致父组件重新render,
    // 而React在render时,不管子组件属性是否改变,都会调用componentWillReceiveProps,
    // 因此,onClick可能会导致componentWillReceiveProps死循环
    // 不过没关系,我们前面加上了终止条件
    onChange(firstNumber);
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值