Front Endreact-native

[React] componentWillReceiveProp

2017-11-15  本文已影响2473人  何幻

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);
};

以上代码新增了isContainedInNumbers判断,
从而可以在selectedNumber被设置后,
避免连续触发componentWillReceiveProps

参考

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

上一篇下一篇

猜你喜欢

热点阅读