React中的原子SetState更新

来自菜鸟教程
跳转至:导航、​搜索

SetState 以误导新人相信状态更新是同步的而臭名昭著。 虽然 setState 通过 协调算法 在计算状态方面非常快,但它仍然是一个异步操作,如果使用不当,可能会使管理 React 组件中的复杂状态变得非常具有挑战性。 在本文中,了解 setState's 鲜为人知的功能签名,它将保证 原子状态更新

SetState Classic™

如果你已经使用 React 一段时间了,你可能遇到过这样的情况:

/*
The initial value of
this.state.count = 0
*/

// multiple calls
this.setState(count: this.state.count + 1);
this.setState(count: this.state.count + 1);
console.log(this.state.count); // 1

// for-loop
for (let i = 0; i < 10; i++) {
  this.setState({count: this.state.count + 1});
}
console.log(this.state.count); // 1

// if-statement
this.setState({count: this.state.count + 1});
if (this.state.count === 0) {
  console.log(this.state.count);  // 0
}

SetState 给 React 社区的新手带来了很多困惑。 使用关键字“react setstate”在 StackOverflow 上快速搜索显示 仍然模糊 关于 setState 是异步还是同步。 Eric Elliot 甚至建议 一起避免 setState 并让 Redux 处理所有状态更新。

这种不满在一定程度上是有道理的。 在 React 官方文档中,这种传递 setState 对象字面量的经典方式很容易引入难以修复的“竞争条件”错误。 当您的组件有很多 state 需要管理时,这个问题会更加复杂。

功能集状态

还有另一种使用 setState 的方法,该方法在文档中没有非常突出地宣传。

this.setState((prevState) => {
  return {count: prevState.count + 1};
})
this.setState((prevState) => {
  return {count: prevState.count + 1};
})
this.setState((prevState) => {
  return {count: prevState.count + 1};
})
this.setState((prevState) => {
  console.log(prevState.count);  // 3
  return {count: prevState.count + 1};
})

如果你以前没有看过这个,你会认为我在编造这个是正确的,但我向你保证 它是真实的 。 通过传入一个函数(而不是熟悉的对象字面量),它将获取当前状态树作为第一个参数,并有机会在将控制权传递给连续的 setState 调用之前执行自己的任意计算。 换句话说,这些函数保证接收原子状态更新。

“使用函数多次调用 setState 是安全的。 更新将被排队,然后按照它们被调用的顺序执行。” – 丹·阿布拉莫夫


这种使用方式setState为我们提供了可预测且可靠的方式来操纵 React 中的状态。 虽然它更冗长,但如果您使用大型状态树和/或复杂的逻辑来更新状态,它就变得不可或缺。

让我们探索一个真实世界的例子,以便我们可以比较这两种方法。

构建密码验证器

想象一下,您正在构建一个表单来让用户重置他们的密码。 我们需要保存几个状态; 其中大部分用于验证密码强度。

class PasswordForm extends Component {

  constructor(props) {
    super(props);
    this.state = {
      password: '',
      hasEnoughChars: false,
      hasUpperAndLowercaseChars: false,
      hasSpecialChars: false,
      isPasswordValid: false
    };
  }

  render() {
    return (
      <div>

        /*input*/
        <input onChange={this.handleInput} type="password" value={this.state.password}/>

        /*visual prompts*/
        <div>
          <span style={bgColor(this.state.hasEnoughChars)}>
            Minimum 8 characters
          </span>
          <span style={bgColor(this.state.hasUpperAndLowercaseChars)}>
            Include 1 uppercase and lowercase letter
          </span>
          <span style={bgColor(this.state.hasSpecialChars)}>
            Minimum 1 special character
          </span>
        </div>

        /*button*/
        <button disabled={!this.state.isPasswordValid}>
          Submit
        </button>

      </div>
    );
  }

  bgColor(condition) {
    return {backgroundColor: condition ? 'green' : 'red'};
  }

  // Object literal & Callback
  handleInput(e) {

    this.setState({
      password: e.target.value,
      hasEnoughChars: e.target.value.length >= 8,
      hasUpperAndLowercaseChars: /[a-z]/.test(e.target.value) && /[A-Z]/.test(e.target.value),
      hasSpecialChars: /[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/.test(e.target.value)
    }, () => {

      if (this.state.hasEnoughChars && this.state.hasUpperAndLowercaseChars && this.state.hasSpecialChars) {
        this.setState({isPasswordValid: true});
      }
      else {
        this.setState({isPasswordValid: false});
      }
    });
  }

  // Functions
  handleInput(e) {

    this.setState({password: e.target.value});

    this.setState((prevState) => ({
      hasEnoughChars: prevState.password.length >= 8,
      hasUpperAndLowercaseChars: /[a-z]/.test(prevState.password) && /[A-Z]/.test(prevState.password),
      hasSpecialChars: /[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/.test(prevState.password)
    }));

    this.setState((prevState) => ({
      isPasswordValid: prevState.hasEnoughChars
        && prevState.hasUpperAndLowercaseChars
        && prevState.hasSpecialChars
    }));
  }
}

使用回调让我们进入通过嵌套函数管理事物顺序的心态。 虽然现在看起来还不错,但如果我们需要在 this.setState({isPasswordValid}) 之后添加回调,事情很快就会变得丑陋。

使用功能,每个setState都是为一个关注领域量身定制的。 出于这个原因,我们不需要在这些函数定义中“硬编码”状态更新的顺序,因为转换状态所需的所有相关信息都在第一个参数中提供:prevState

包起来

通过对 setState 使用此函数签名,您可以直接使用最新的状态树。 这种可预测性使我们能够更有效地推理问题,并自信地构建状态丰富的 React 组件。

试试这个模式,看看你是否喜欢它。 这种使用 setState 的方式有可能成为事实上的。 在 tweet 中,Dan Abramov 建议“未来 React 很可能最终会更直接地支持这种模式。”

👉查看密码表格的CodePen