React 方法中的多个 setState() 调用:如何使其工作 "synchronously"

Multiple setState() calls in a React method: How to make it work "synchronously"

所以我在我的 React 应用程序中遇到了问题,我 运行 进入了一个特定的案例,我需要在一个方法中进行多次 setState() 调用,然后有代码 运行 之后设置状态。下面的代码是一个用于在网站上添加帐户的对话框。

import React from 'react';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import TextField from 'material-ui/TextField';

/**
 * A modal dialog can only be closed by selecting one of the actions.
 */
export default class NewAcctDia extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      open: false,
      userError: null,
      passError: null,
      passConfirmError: null,
    }

    this.handleOpen = this.handleOpen.bind(this);
    this.handleClose = this.handleClose.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleOpen() {
    this.setState({open: true});
  }

  handleClose() {
    this.setState({open: false});
  }

  handleSubmit() {
    if(!this.refs.user.getValue())
      this.setState({userError: "This field is required"});
    else
      this.setState({userError: null});

    if(!this.refs.pass.getValue())
      this.setState({passError: "This field is required"});
    else
      this.setState({passError: null});

    if(this.refs.pass.getValue() == this.refs.passConfirm.getValue()) {
      this.setState({passError: null});
    } else {
      this.setState({passConfirmError: "Passwords do not match"});
    }

    if(!this.state.userError && !this.state.passError && !this.state.passConfirmError)
      alert('worked');
  }

  render() {
    const actions = [
      <FlatButton
        label="Cancel"
        primary={true}
        onTouchTap={this.handleClose}
      />,
      <FlatButton
        label="Submit"
        primary={true}
        disabled={false}
        onTouchTap={this.handleSubmit}
      />,
    ];

    return (
        <Dialog
          title="Create an Account"
          actions={actions}
          modal={true}
          open={this.state.open}
          contentStyle={{width: 350}}
        >
          <TextField
            ref='user'
            floatingLabelText="Username"
            errorText={this.state.userError}
          /><br />
          <TextField
            ref='pass'
            floatingLabelText="Password"
            type="password"
            errorText={this.state.passError}
          /><br />
          <TextField
            ref='passConfirm'
            floatingLabelText="Confirm Password"
            type="password"
            errorText={this.state.passConfirmError}
          /><br />
        </Dialog>
    );
  }
}

问题出在 handleSubmit() 方法中,我需要检查用户是否在用户名和密码字段中输入了内容,以及密码和确认密码字段是否匹配。如果他们不这样做,我会在需要通过状态更改的字段中添加错误文本。然后我尝试查看状态以查看是否有任何错误。

不幸的是,正如我很快发现的那样,setState() 函数是异步的,这意味着在我最终检查之前状态不会改变。我在谷歌上搜索并搜索了一种在执行代码之前等待状态更改的方法,但结果是空的。我现在已经解决了这个问题,我想我会把它放在 Stack 上,这样其他人可能会从我提出的方法中受益。我也想知道我正在做的事情的利弊,或者任何可能更好的建议。

当我四处搜索时,我发现了一种将回调发送到 setState() 的方法,如图所示:setState(data, callback)。一开始我认为这对我不起作用,因为我有多个 setState() 电话。但是,我意识到我可以将 handleSubmit() 方法转换为使用三元组的单个 setState() 调用。像这样:

handleSubmit() {
    this.setState({
      userError: (
        this.refs.user.getValue() ? null : "This field is required"
      ),
      passError: (
        this.refs.pass.getValue() ? null : "This field is required"
      ),
      passConfirmError: (
        (this.refs.pass.getValue() == this.refs.passConfirm.getValue()) ? 
          null : "Passwords do not match"
      )
    }, () => {
      if(!this.state.userError && !this.state.passError && !this.state.passConfirmError)
        alert('worked');
    })
}

状态改变后会执行匿名回调函数,让我的检查生效。

我预见到此方法的唯一问题是嵌套三元组,因为如果需要它们可能会变得非常混乱。这对我的程序有什么不利影响吗?或者我可以用更好的方法解决这个问题?

我希望我的解决方案帮助了一些人。 :D

Reactdocumentation鼓励使用componentDidUpdate代替setState的回调参数。

但是,如果您认为代码看起来有点混乱,请尝试使用局部常量并调用 setState:

handleSubmit() {
  const {user, pass, passConfirm} = this.refs;
  const userError = user.getValue() ? null : "This field is required";
  const passError = pass.getValue() ? null : "This field is required";
  const passConfirmError = !passError && pass.getValue() === passConfirm.getValue()
    ? null
    : "Passwords do not match";

  this.setState({userError, passError, passConfirmError});

  if(!userError && !emptyPassError && !passConfirmError)
    alert('worked');
}

最后,documentation 还建议在字符串引用上使用回调引用:

Using the ref callback just to set a property on the class is a common pattern for accessing DOM elements. If you are currently using this.refs.myRefName to access refs, we recommend using this pattern instead.