美文网首页Front Endreact-native
[React] componentWillReceiveProp

[React] componentWillReceiveProp

作者: 何幻 | 来源:发表于2017-11-15 14:08 被阅读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

    相关文章

      网友评论

        本文标题:[React] componentWillReceiveProp

        本文链接:https://www.haomeiwen.com/subject/zqicvxtx.html