为什么在React中componentWillReceiveprops在componentDidMount中的setState()之前触发?

时间:2018-08-30 08:45:22

标签: reactjs lifecycle setstate react-props

我已经用React编程了一段时间了,但是我从未遇到过这个烦人的问题,在componentWillReceiveProps中的setState()执行之前,我的一个组件componentDidMount被触发了。这会在我的应用程序中引起几个问题。

我从prop收到一个变量this.props.flag,该变量将存储在组件状态下:

    componentDidMount() {
        if (!_.isEmpty(this.props.flag)) {

            console.log('Flag Did:', this.props.flag);

            this.setState({
                flag: this.props.flag
            },
                () => doSomething()
            );
    }

在我的componentWillReceiveProps方法中,变量this.state.flag将被替换,即使它为空或与this.props.flag的值不同(使用lodash进行检查)库):

componentWillReceiveProps(nextProps) {
    const { flag } = this.state;

    console.log('Flag Will:', !_.isEqual(flag, nextProps.flag), flag, nextProps.flag);

    if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag)) {
            this.setState({
                flag: nextProps.flag,
            },
                () => doSomething()
            );
    }
}

假设在这种情况下,道具flag始终具有相同的值,并且this.state.flag被初始化为undefined。当我检查控制台日志时,看到以下结果:

Flag Did: true
Flag Will: true undefined true

因此,当代码输入componentWillReceiveProps时,this.state.flag的值仍为undefined,这意味着setState中的componentDidMount尚未设置。

这与React生命周期不一致,或者我缺少什么吗?我该如何避免这种行为?

2 个答案:

答案 0 :(得分:1)

在道具更新(父组件重新渲染)引起的每个更新生命周期中,都会调用

ComponentWillReceiveProps()。由于Javascript是同步的,因此有时您可能需要验证道具来保存应用程序崩溃。我尚未完全了解您的应用程序的上下文,但是您可以做的是:

componentWillReceiveProps(nextProps) {
    const { flag } = this.state;

    if(!flag){
      return;, 
    }

    console.log('Flag Will:', !_.isEqual(flag, nextProps.flag), flag, nextProps.flag);

    if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag)) {
            this.setState({
                flag: nextProps.flag,
            },
                () => doSomething()
            );
    }
}

如果状态未定义,则可以返回。父级重新渲染后将再次调用它。但这可能不是用例。

无论如何,您都应该研究this

  

但是我可以想到至少有1种(可能是理论上的)情形,其顺序将被反转:

     

组件接收道具,并开始渲染。而组件是   渲染,但尚未完成渲染,组件收到新的   道具。触发componentWillReceiveProps(),(但componentDidMount   还没有被解雇)所有子代和组件本身都拥有   完成渲染后,componentDidMount()将触发。所以   componentDidMount()不是初始化的好地方   像您的{foo:'bar'}这样的组件变量。 componentWillMount()   会是一个更好的生命周期事件。但是,我不鼓励任何使用   反应组件内部组件范围内的变量,并坚持   设计原则:

     

所有组件变量都应处于状态或道具(并且   不可变的),所有其他变量都受生命周期方法约束(并且   不超过此范围)

答案 1 :(得分:0)

根据用户JJJ的建议,鉴于setState的异步性质,if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag))中的检查componentWillReceivePropssetState内部的componentDidMount执行{{ 1}}。操作顺序为:

  1. 代码输入flag: this.props.flag
  2. 代码在以下位置执行setState componentDidMount(componentDidMount尚未发生)。
  3. 代码退出flag: this.props.flagcomponentDidMount中的setState是 仍在执行中(componentDidMount尚未发生)。
  4. 组件收到新道具,因此进入 flag: this.props.flag
  5. 中的语句componentWillReceiveProps if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag))已执行(componentWillReceiveProps仍在 未定义)。
  6. 代码完成内部this.state.flag的执行 setState并设置componentDidMount并执行 flag: this.props.flag
  7. 代码完成内部doSomething()的执行 setState并设置componentWillMount并执行 flag: nextProps.flag

鉴于doSomething()的异步特性,6和7可以并行执行,因此我们不知道哪个将首先完成其执行。在这种情况下,setState被调用至少两次,而必须被调用一次。

为了解决这些问题,我以这种方式更改了代码:

DoSomething()

这样,我将道具的新版本(componentWillReceiveProps(nextProps) { if (!_.isEmpty(nextProps.flag) && !_.isEqual(this.props.flag, nextProps.flag)) { this.setState({ flag: nextProps.flag, }, () => doSomething() ); } } )与旧版本(nextProps)进行比较,而无需等待this.props值存储在组件状态中。