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

问题描述 投票:0回答:2

我一直在用React编程一段时间,但我从来没有遇到过这个恼人的问题,在componentWillReceivePropssetState()被执行之前,我的一个组件componentDidMount开火了。这会在我的应用程序中导致一些问题

我有一个从道具收到的变量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.flagis的值仍然是undefined,这意味着setState中的componentDidMount尚未设置。

这与React生命周期不一致或者我错过了什么?我该如何避免这种行为?

reactjs lifecycle setstate react-props
2个回答
1
投票

ComponentWillReceiveProps()将在每个更新生命周期中被调用,因为更改了props(父组件重新呈现)。由于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()将是一个更好的生命周期事件。但是,我不鼓励在反应组件中使用组件范围的变量,并坚持设计原则:

所有组件变量都应该存在于state或props中(并且是不可变的)所有其他变量都受生命周期方法的约束(并且不会超出该范围)


0
投票

正如用户JJJ所建议的那样,鉴于setState的异步性质,if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag))中的componentWillReceiveProps检查是在setState执行componentDidMount之前执行的flag: this.props.flag之前执行的。操作的顺序是:

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

鉴于setState的异步性质,6和7可以并行执行,因此我们不知道哪个将首先完成其执行。在这种情况下,DoSomething()在被调用一次时可能被称为至少2次。

为了解决这些问题,我改变了我的代码:

componentWillReceiveProps(nextProps)  {

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

这样我就可以将新版本(nextProps)与道具的旧版本(this.props)进行比较,而无需等待flag值存储在组件的状态中。

© www.soinside.com 2019 - 2024. All rights reserved.