虽然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文档中,这种经典的方式通过设置状态
一个对象的字面上倾向于引入种族状态
错误,这是很难修复的。
功能定位
还有另一种使用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 中的状态,虽然它更有说法,但如果您正在使用大型状态树和 / 或复杂的逻辑来更新状态,它就变得不可或缺。
让我们探索一个现实世界的例子,以便我们可以比较这两种方法。
创建一个密码验证器
想象一下,你正在构建一个表单,让用户重置他们的密码,我们需要保持几个状态,其中大部分用于验证密码强度。
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