如何从 `componentDidUpdate` 中调用 `dispatch` 而不会陷入无限循环?

How can I call `dispatch` from within `componentDidUpdate` without ending up in a infinite loop?

我正在制作一个非常简单的 React 组件,它显示从远程源获取的数据。在获取数据之前,它必须等待用户正确登录。这是我尝试执行此操作的方法:

class MyComponent extends React.Component {
  componentDidUpdate () {
    if (this.props.loggedIn) {
      api.getData()
        .then(() => {
          this.props.dispatch({
            type: 'gotData'
          })
        }, (reason) => {
          this.props.dispatch({
            type: 'getDataFailed'
          })
        })
    }
  }
}

用我自己的话说,每次与这个组件相关的状态的一部分(在本例中,loggedIn prop)被更新,componentDidUpdate被调用,我可以得到数据如果我看到用户已登录。

这实际上工作得很好,除了一个主要问题:似乎调用 dispatch 最终会再次触发 componentDidUpdate,所以我陷入了无限循环。我什至不必在任何地方监听这些调度事件,使用 dispatch 函数的简单事实足以再次触发 componentDidUpdate

我的猜测是 dispatch 执行了我内部使用 setState 的 root reducer,从而再次触发了整个 update 生命周期链。

这种事情一般是怎么做的?如何从 componentDidUpdate 中调用 dispatch 而不会陷入无限循环?

解决这个问题的一个简单方法是添加一个额外的标志,例如 loggingIn,您在异步操作之前设置(通过调度)并在之后重置。您还需要跟踪登录失败的时间,以区分这种情况与登录过程未启动的时间(除非您想在失败时自动重新启动该过程):

class MyComponent extends React.Component {
  componentDidUpdate () {
    const { loggedIn, loggingIn, loginFailed, dispatch } = this.props;
    if (!loggingIn && !loggedIn && ! loginFailed) {
      dispatch({
        type: 'gettingData' // this one sets loggingIn to true, loggedIn to false, loginFailed to false
      });
      api.getData()
        .then(() => {
          dispatch({
            type: 'gotData' // this one sets loggingIn to false, loggedIn to true, loginFailed to false
          })
        }, (reason) => {
          dispatch({
            type: 'getDataFailed' // this one sets loggingIn to false, loggedIn to false, loginFailed to true
          })
        })
    }
  }
}

如果你不想在 redux 中设置这个状态,你也可以让这个控件存在于你的组件中:

class MyComponent extends React.Component {
  componentDidUpdate () {
    const { loggedIn, dispatch } = this.props;
    const { loggingIn, loginFailed } = this.state;

    if (!loggingIn && !loggedIn && !loginFailed) {
      this.setState({
        loggingIn: true
      })
      api.getData()
        .then(() => {
          this.setState({
            loggingIn: false,
            loginFailed: false
          });
          dispatch({
            type: 'gotData'
          })
        }, (reason) => {
          this.setState({
            loggingIn: false,
            loginFailed: true
          });
          dispatch({
            type: 'getDataFailed'
          })
        })
    }
  }
}

您可以使用 loggingIn 标志向您的用户显示微调框,或者使用 loginFailed 标志显示一条​​消息,如果这适合您的用户体验。

当您收到登录状态后,请将您的 api 号召性用语移至号召性用语!像这样:

api.getUserLoggedInStatus().then((status) => {
  dispatch({
    type: 'loggedInStatus',
    status
  });
  api.getData.... // and dispatch
};

此流程将避免任何循环或竞争条件。