React 中的原子设置状态更新

虽然setState通过它的 调和算法非常快地计算状态,但它仍然是一个非同步操作,如果使用不当,可以使它在React组件中管理复杂状态非常困难。

经典定位

如果您已使用 React 一段时间,您可能会遇到这样的情况:

 1/*
 2The initial value of
 3this.state.count = 0
 4*/
 5
 6// multiple calls
 7this.setState(count: this.state.count + 1);
 8this.setState(count: this.state.count + 1);
 9console.log(this.state.count); // 1
10
11// for-loop
12for (let i = 0; i < 10; i++) {
13  this.setState({count: this.state.count + 1});
14}
15console.log(this.state.count); // 1
16
17// if-statement
18this.setState({count: this.state.count + 1});
19if (this.state.count === 0) {
20  console.log(this.state.count);  // 0
21}

SetState给 React 社区的新人带来了很多困惑。使用react setstate的关键字在 StackOverflow 上进行快速搜索显示,关于setState是否非同步或同步,仍然存在(https://stackoverflow.com/search?q=react+setstate)雾霾。

在官方的React文档中,这种经典的方式通过设置状态一个对象的字面上倾向于引入种族状态错误,这是很难修复的。

Silly state illustration

功能定位

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

 1this.setState((prevState) => {
 2  return {count: prevState.count + 1};
 3})
 4this.setState((prevState) => {
 5  return {count: prevState.count + 1};
 6})
 7this.setState((prevState) => {
 8  return {count: prevState.count + 1};
 9})
10this.setState((prevState) => {
11  console.log(prevState.count);  // 3
12  return {count: prevState.count + 1};
13})

如果你以前没有看到这个,你会认为我正在编写这个,但我向你保证(https://facebook.github.io/react/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous)。通过传入函数(而不是熟悉的对象字面)它将获得当前状态树作为第一个论点,并有机会进行自己的任意计算,然后它将控制转移到连续的setState呼叫。

<$>[注]`使用函数多次呼叫 setState 是安全的,更新将排队,然后按照呼叫的顺序执行。

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

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

password box illustration

创建一个密码验证器

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

 1class PasswordForm extends Component {
 2
 3  constructor(props) {
 4    super(props);
 5    this.state = {
 6      password: '',
 7      hasEnoughChars: false,
 8      hasUpperAndLowercaseChars: false,
 9      hasSpecialChars: false,
10      isPasswordValid: false
11    };
12  }
13
14  render() {
15    return (
16      <div>
17
18        /*input*/
19        <input onChange={this.handleInput} type="password" value={this.state.password}/>
20
21        /*visual prompts*/
22        <div>
23          <span style={bgColor(this.state.hasEnoughChars)}>
24            Minimum 8 characters
25          </span>
26          <span style={bgColor(this.state.hasUpperAndLowercaseChars)}>
27            Include 1 uppercase and lowercase letter
28          </span>
29          <span style={bgColor(this.state.hasSpecialChars)}>
30            Minimum 1 special character
31          </span>
32        </div>
33
34        /*button*/
35        <button disabled={!this.state.isPasswordValid}>
36          Submit
37        </button>
38
39      </div>
40    );
41  }
42
43  bgColor(condition) {
44    return {backgroundColor: condition ? 'green' : 'red'};
45  }
46
47  // Object literal & Callback
48  handleInput(e) {
49
50    this.setState({
51      password: e.target.value,
52      hasEnoughChars: e.target.value.length >= 8,
53      hasUpperAndLowercaseChars: /[a-z]/.test(e.target.value) && /[A-Z]/.test(e.target.value),
54      hasSpecialChars: /[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/.test(e.target.value)
55    }, () => {
56
57      if (this.state.hasEnoughChars && this.state.hasUpperAndLowercaseChars && this.state.hasSpecialChars) {
58        this.setState({isPasswordValid: true});
59      }
60      else {
61        this.setState({isPasswordValid: false});
62      }
63    });
64  }
65
66  // Functions
67  handleInput(e) {
68
69    this.setState({password: e.target.value});
70
71    this.setState((prevState) => ({
72      hasEnoughChars: prevState.password.length >= 8,
73      hasUpperAndLowercaseChars: /[a-z]/.test(prevState.password) && /[A-Z]/.test(prevState.password),
74      hasSpecialChars: /[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/.test(prevState.password)
75    }));
76
77    this.setState((prevState) => ({
78      isPasswordValid: prevState.hasEnoughChars
79        && prevState.hasUpperAndLowercaseChars
80        && prevState.hasSpecialChars
81    }));
82  }
83}

使用回调将我们置于 管理事物的顺序的思维中,通过嵌入函数. 虽然现在看起来不那么糟糕,如果我们需要在this.setState({isPasswordValid})之后添加回调,事情就会很快变得丑陋。

由于这个原因,我们不需要在这些函数定义中硬编码状态更新的顺序,因为在第一个参数中提供过渡状态所需的所有相关信息:prevState

包装上

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

试试这个模式,看看你是否喜欢它。有可能这种使用setState的方式将成为事实。在一个推文中(https://twitter.com/dan_abramov/status/824315688093421568),Dan Abramov建议`很可能在未来React最终将更直接地支持这个模式。

👉 查看密码表格的 CodePen

Published At
Categories with 技术
Tagged with
comments powered by Disqus