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